import type { CreatorIntentReadiness, CustomWorldPendingClarification, } from '../../packages/shared/src/contracts/customWorldAgent'; import type { ActorAnchor, CreatorCharacterSeed, CreatorFactionSeed, CreatorLandmarkSeed, CustomWorldAnchorPack, CustomWorldCreatorInputMode, CustomWorldCreatorIntent, CustomWorldLockState, LandmarkAnchor, } from '../types'; export type CustomWorldCreatorIntentPatch = Partial< Pick< CustomWorldCreatorIntent, | 'rawSettingText' | 'worldHook' | 'themeKeywords' | 'toneDirectives' | 'playerPremise' | 'openingSituation' | 'coreConflicts' | 'keyFactions' | 'keyCharacters' | 'keyLandmarks' | 'iconicElements' | 'forbiddenDirectives' > >; export type CustomWorldCreatorIntentReplaceableField = | 'rawSettingText' | 'worldHook' | 'themeKeywords' | 'toneDirectives' | 'playerPremise' | 'openingSituation' | 'coreConflicts' | 'keyFactions' | 'keyCharacters' | 'keyLandmarks' | 'iconicElements' | 'forbiddenDirectives'; export type CustomWorldCreatorIntentPatchInput = CustomWorldCreatorIntentPatch & { replaceFields?: CustomWorldCreatorIntentReplaceableField[]; }; type CreatorIntentReadinessKey = | 'world_hook' | 'player_premise' | 'theme_and_tone' | 'core_conflict' | 'relationship_seed' | 'iconic_element'; function toText(value: unknown) { return typeof value === 'string' ? value.trim() : ''; } function toStringArray(value: unknown, maxCount = 8) { if (!Array.isArray(value)) { return []; } return [...new Set(value.map((item) => toText(item)).filter(Boolean))].slice( 0, maxCount, ); } function slugify(value: string) { const normalized = value .trim() .toLowerCase() .replace(/[^a-z0-9\u4e00-\u9fa5]+/g, '-') .replace(/^-+|-+$/g, ''); return normalized || 'entry'; } function createSeedId(prefix: string, label: string, index: number) { return `${prefix}-${slugify(label || `${prefix}-${index + 1}`)}-${index + 1}`; } function clampText(value: string, maxLength: number) { const normalized = value.trim().replace(/\s+/g, ' '); if (!normalized) { return ''; } if (normalized.length <= maxLength) { return normalized; } return `${normalized.slice(0, Math.max(0, maxLength - 1)).trim()}…`; } function normalizeCreatorFactionSeed( value: unknown, index: number, ): CreatorFactionSeed | null { if (!value || typeof value !== 'object') { return null; } const item = value as Record; const name = toText(item.name); const publicGoal = toText(item.publicGoal); const tension = toText(item.tension); const notes = toText(item.notes); if (!name && !publicGoal && !tension && !notes) { return null; } return { id: toText(item.id) || createSeedId('creator-faction', name || publicGoal, index), name, publicGoal, tension, notes, locked: Boolean(item.locked), }; } function normalizeCreatorCharacterSeed( value: unknown, index: number, ): CreatorCharacterSeed | null { if (!value || typeof value !== 'object') { return null; } const item = value as Record; const name = toText(item.name); const role = toText(item.role); const publicMask = toText(item.publicMask); const hiddenHook = toText(item.hiddenHook); const relationToPlayer = toText(item.relationToPlayer); const notes = toText(item.notes); if ( !name && !role && !publicMask && !hiddenHook && !relationToPlayer && !notes ) { return null; } return { id: toText(item.id) || createSeedId('creator-character', name || role || publicMask, index), name, role, publicMask, hiddenHook, relationToPlayer, notes, locked: Boolean(item.locked), }; } function normalizeCreatorLandmarkSeed( value: unknown, index: number, ): CreatorLandmarkSeed | null { if (!value || typeof value !== 'object') { return null; } const item = value as Record; const name = toText(item.name); const purpose = toText(item.purpose); const mood = toText(item.mood); const secret = toText(item.secret); if (!name && !purpose && !mood && !secret) { return null; } return { id: toText(item.id) || createSeedId('creator-landmark', name || purpose || mood, index), name, purpose, mood, secret, locked: Boolean(item.locked), }; } function normalizeAnchorArray( value: unknown, normalizer: (value: unknown, index: number) => T | null, maxCount: number, ) { if (!Array.isArray(value)) { return []; } return value .map((item, index) => normalizer(item, index)) .filter((item): item is T => Boolean(item)) .slice(0, maxCount); } function mergeStringArray( base: string[], patch: string[] | undefined, maxCount: number, ) { if (!patch || patch.length === 0) { return [...base]; } return [ ...new Set([...base, ...patch.map((item) => toText(item)).filter(Boolean)]), ].slice(0, maxCount); } function mergeNarrativeText(base: string, patch: string | undefined) { const nextText = toText(patch); if (!nextText) { return base; } if (!base) { return nextText; } if (base.includes(nextText)) { return base; } return `${base}\n${nextText}`.trim(); } function mergeSeedArray( base: T[], patch: T[] | undefined, maxCount: number, mergeEntry: (current: T, next: T) => T, ) { if (!patch || patch.length === 0) { return [...base]; } const nextItems = [...base]; patch.forEach((entry) => { const normalizedName = toText(entry.name); const existingIndex = nextItems.findIndex( (item) => item.id === entry.id || (normalizedName && toText(item.name).toLowerCase() === normalizedName.toLowerCase()), ); if (existingIndex >= 0) { const currentItem = nextItems[existingIndex]; if (!currentItem) { nextItems.push(entry); return; } nextItems[existingIndex] = mergeEntry(currentItem, entry); return; } nextItems.push(entry); }); return nextItems.slice(0, maxCount); } function mergeCharacterSeed( current: CreatorCharacterSeed, next: CreatorCharacterSeed, ): CreatorCharacterSeed { return { ...current, ...next, id: next.id || current.id, name: toText(next.name) || current.name, role: toText(next.role) || current.role, publicMask: toText(next.publicMask) || current.publicMask, hiddenHook: toText(next.hiddenHook) || current.hiddenHook, relationToPlayer: toText(next.relationToPlayer) || current.relationToPlayer, notes: toText(next.notes) || current.notes, locked: typeof next.locked === 'boolean' ? next.locked : Boolean(current.locked), }; } function mergeFactionSeed( current: CreatorFactionSeed, next: CreatorFactionSeed, ): CreatorFactionSeed { return { ...current, ...next, id: next.id || current.id, name: toText(next.name) || current.name, publicGoal: toText(next.publicGoal) || current.publicGoal, tension: toText(next.tension) || current.tension, notes: toText(next.notes) || current.notes, locked: typeof next.locked === 'boolean' ? next.locked : Boolean(current.locked), }; } function mergeLandmarkSeed( current: CreatorLandmarkSeed, next: CreatorLandmarkSeed, ): CreatorLandmarkSeed { return { ...current, ...next, id: next.id || current.id, name: toText(next.name) || current.name, purpose: toText(next.purpose) || current.purpose, mood: toText(next.mood) || current.mood, secret: toText(next.secret) || current.secret, locked: typeof next.locked === 'boolean' ? next.locked : Boolean(current.locked), }; } export function createEmptyCustomWorldCreatorIntent( sourceMode: CustomWorldCreatorInputMode = 'freeform', ): CustomWorldCreatorIntent { return { sourceMode, rawSettingText: '', worldHook: '', themeKeywords: [], toneDirectives: [], playerPremise: '', openingSituation: '', coreConflicts: [], keyFactions: [], keyCharacters: [], keyLandmarks: [], iconicElements: [], forbiddenDirectives: [], }; } export function normalizeCustomWorldCreatorIntent( value: unknown, fallbackMode: CustomWorldCreatorInputMode = 'freeform', ): CustomWorldCreatorIntent | null { if (!value || typeof value !== 'object') { return null; } const item = value as Record; const sourceMode = item.sourceMode === 'card' || item.sourceMode === 'freeform' ? item.sourceMode : fallbackMode; const rawSettingText = toText(item.rawSettingText); const worldHook = toText(item.worldHook); const playerPremise = toText(item.playerPremise); const openingSituation = toText(item.openingSituation); const themeKeywords = toStringArray(item.themeKeywords, 8); const toneDirectives = toStringArray(item.toneDirectives, 8); const coreConflicts = toStringArray(item.coreConflicts, 6); const iconicElements = toStringArray(item.iconicElements, 8); const forbiddenDirectives = toStringArray(item.forbiddenDirectives, 8); const keyFactions = normalizeAnchorArray( item.keyFactions, normalizeCreatorFactionSeed, 6, ); const keyCharacters = normalizeAnchorArray( item.keyCharacters, normalizeCreatorCharacterSeed, 8, ); const keyLandmarks = normalizeAnchorArray( item.keyLandmarks, normalizeCreatorLandmarkSeed, 8, ); if ( !rawSettingText && !worldHook && themeKeywords.length === 0 && toneDirectives.length === 0 && !playerPremise && !openingSituation && coreConflicts.length === 0 && keyFactions.length === 0 && keyCharacters.length === 0 && keyLandmarks.length === 0 && iconicElements.length === 0 && forbiddenDirectives.length === 0 ) { return null; } return { sourceMode, rawSettingText, worldHook, themeKeywords, toneDirectives, playerPremise, openingSituation, coreConflicts, keyFactions, keyCharacters, keyLandmarks, iconicElements, forbiddenDirectives, }; } export function mergeCustomWorldCreatorIntent( current: CustomWorldCreatorIntent | null | undefined, patch: CustomWorldCreatorIntentPatchInput | null | undefined, fallbackMode: CustomWorldCreatorInputMode = 'freeform', ) { if (!patch) { return current ? normalizeCustomWorldCreatorIntent(current, fallbackMode) : createEmptyCustomWorldCreatorIntent(fallbackMode); } const base = normalizeCustomWorldCreatorIntent(current, fallbackMode) ?? createEmptyCustomWorldCreatorIntent(fallbackMode); const replaceFields = new Set(patch.replaceFields ?? []); const patchIntent = normalizeCustomWorldCreatorIntent( { sourceMode: base.sourceMode, ...patch, }, base.sourceMode, ) ?? createEmptyCustomWorldCreatorIntent(base.sourceMode); return { ...base, rawSettingText: replaceFields.has('rawSettingText') ? toText(patchIntent.rawSettingText) || base.rawSettingText : mergeNarrativeText(base.rawSettingText, patchIntent.rawSettingText), worldHook: toText(patchIntent.worldHook) || base.worldHook, themeKeywords: replaceFields.has('themeKeywords') ? [...patchIntent.themeKeywords] : mergeStringArray(base.themeKeywords, patchIntent.themeKeywords, 8), toneDirectives: replaceFields.has('toneDirectives') ? [...patchIntent.toneDirectives] : mergeStringArray(base.toneDirectives, patchIntent.toneDirectives, 8), playerPremise: toText(patchIntent.playerPremise) || base.playerPremise, openingSituation: toText(patchIntent.openingSituation) || base.openingSituation, coreConflicts: replaceFields.has('coreConflicts') ? [...patchIntent.coreConflicts] : mergeStringArray(base.coreConflicts, patchIntent.coreConflicts, 6), keyFactions: replaceFields.has('keyFactions') ? [...patchIntent.keyFactions] : mergeSeedArray( base.keyFactions, patchIntent.keyFactions, 6, mergeFactionSeed, ), keyCharacters: replaceFields.has('keyCharacters') ? [...patchIntent.keyCharacters] : mergeSeedArray( base.keyCharacters, patchIntent.keyCharacters, 8, mergeCharacterSeed, ), keyLandmarks: replaceFields.has('keyLandmarks') ? [...patchIntent.keyLandmarks] : mergeSeedArray( base.keyLandmarks, patchIntent.keyLandmarks, 8, mergeLandmarkSeed, ), iconicElements: replaceFields.has('iconicElements') ? [...patchIntent.iconicElements] : mergeStringArray(base.iconicElements, patchIntent.iconicElements, 8), forbiddenDirectives: replaceFields.has('forbiddenDirectives') ? [...patchIntent.forbiddenDirectives] : mergeStringArray( base.forbiddenDirectives, patchIntent.forbiddenDirectives, 8, ), } satisfies CustomWorldCreatorIntent; } export function evaluateCustomWorldCreatorIntentReadiness( intent: CustomWorldCreatorIntent | null | undefined, ): CreatorIntentReadiness { const normalized = normalizeCustomWorldCreatorIntent(intent) ?? createEmptyCustomWorldCreatorIntent('freeform'); const completedKeys: CreatorIntentReadinessKey[] = []; const missingKeys: CreatorIntentReadinessKey[] = []; const relationshipReady = normalized.keyCharacters.some( (entry) => Boolean(toText(entry.name)) && Boolean(toText(entry.relationToPlayer) || toText(entry.hiddenHook)), ); const keyChecks: Array<{ key: CreatorIntentReadinessKey; ready: boolean; }> = [ { key: 'world_hook', ready: normalized.worldHook.trim().length >= 8 || normalized.rawSettingText.trim().length >= 24, }, { key: 'player_premise', ready: Boolean( normalized.playerPremise.trim() && normalized.openingSituation.trim(), ), }, { key: 'theme_and_tone', ready: normalized.themeKeywords.length >= 1 && normalized.toneDirectives.length >= 1, }, { key: 'core_conflict', ready: normalized.coreConflicts.length >= 1, }, { key: 'relationship_seed', ready: normalized.keyCharacters.length >= 1 && relationshipReady, }, { key: 'iconic_element', ready: normalized.iconicElements.length >= 1, }, ]; keyChecks.forEach((entry) => { if (entry.ready) { completedKeys.push(entry.key); return; } missingKeys.push(entry.key); }); return { isReady: missingKeys.length === 0, completedKeys, missingKeys, }; } const CLARIFICATION_DEFINITIONS: Array<{ targetKey: CreatorIntentReadinessKey; priority: number; label: string; question: string; }> = [ { targetKey: 'world_hook', priority: 1, label: '世界一句话', question: '先用一句话说清,这个世界最独特的核心幻想是什么?可以直接给我一句钉住调性的描述。', }, { targetKey: 'player_premise', priority: 2, label: '玩家身份与开局', question: '玩家是谁,故事开场时正卡在什么局面里?你可以直接把身份和开局困境一起告诉我。', }, { targetKey: 'core_conflict', priority: 3, label: '核心冲突', question: '现在这个世界最主要的冲突是什么?最好是能立刻推动剧情的那种对抗或危机。', }, { targetKey: 'theme_and_tone', priority: 4, label: '主题气质', question: '你想要它整体更偏什么主题和气质?比如克制、压迫、浪漫、冷峻,或者明确不要什么。', }, { targetKey: 'relationship_seed', priority: 5, label: '关键关系钩子', question: '给我一个最值得写的关键人物种子就行,他和玩家是什么关系,或者身上藏着什么暗线?', }, { targetKey: 'iconic_element', priority: 6, label: '标志性要素', question: '这个世界有什么一眼就能认出来的标志性元素、意象或硬规则?先给 1 到 2 个就够。', }, ]; export function buildPendingClarifications( intent: CustomWorldCreatorIntent | null | undefined, readiness = evaluateCustomWorldCreatorIntentReadiness(intent), ) { return CLARIFICATION_DEFINITIONS.filter((entry) => readiness.missingKeys.includes(entry.targetKey), ) .sort((left, right) => left.priority - right.priority) .slice(0, 3) .map( (entry): CustomWorldPendingClarification => ({ id: entry.targetKey, label: entry.label, question: entry.question, targetKey: entry.targetKey, priority: entry.priority, }), ); } export function normalizeCustomWorldLockState( value: unknown, ): CustomWorldLockState { if (!value || typeof value !== 'object') { return { worldLockedFields: [], lockedCharacterIds: [], lockedLandmarkIds: [], lockedConflictIds: [], lockedFactionIds: [], }; } const item = value as Record; return { worldLockedFields: toStringArray(item.worldLockedFields, 12), lockedCharacterIds: toStringArray(item.lockedCharacterIds, 20), lockedLandmarkIds: toStringArray(item.lockedLandmarkIds, 20), lockedConflictIds: toStringArray(item.lockedConflictIds, 20), lockedFactionIds: toStringArray(item.lockedFactionIds, 20), }; } export function deriveCustomWorldLockStateFromIntent( intent: CustomWorldCreatorIntent | null | undefined, ): CustomWorldLockState { return { worldLockedFields: [], lockedCharacterIds: intent?.keyCharacters .filter((entry) => entry.locked) .map((entry) => entry.id) ?? [], lockedLandmarkIds: intent?.keyLandmarks .filter((entry) => entry.locked) .map((entry) => entry.id) ?? [], lockedConflictIds: [], lockedFactionIds: intent?.keyFactions .filter((entry) => entry.locked) .map((entry) => entry.id) ?? [], }; } export function hasMeaningfulCustomWorldCreatorIntent( intent: CustomWorldCreatorIntent | null | undefined, ) { return Boolean( intent && (intent.rawSettingText || intent.worldHook || intent.themeKeywords.length > 0 || intent.toneDirectives.length > 0 || intent.playerPremise || intent.openingSituation || intent.coreConflicts.length > 0 || intent.keyFactions.length > 0 || intent.keyCharacters.length > 0 || intent.keyLandmarks.length > 0 || intent.iconicElements.length > 0 || intent.forbiddenDirectives.length > 0), ); } function buildAnchorLine(label: string, content: string) { return content ? `${label}:${content}` : ''; } export function buildCustomWorldCreatorIntentFoundationText( intent: CustomWorldCreatorIntent | null | undefined, ) { if (!hasMeaningfulCustomWorldCreatorIntent(intent)) { return ''; } const relationshipSeed = intent?.keyCharacters[0]; const relationshipText = relationshipSeed ? [ relationshipSeed.name, relationshipSeed.role, relationshipSeed.relationToPlayer ? `与玩家 ${relationshipSeed.relationToPlayer}` : '', relationshipSeed.hiddenHook ? `暗线 ${relationshipSeed.hiddenHook}` : '', ] .filter(Boolean) .join(' · ') : ''; const playerOpeningText = [intent?.playerPremise || '', intent?.openingSituation || ''] .filter(Boolean) .join(';'); const themeToneText = [ intent?.themeKeywords.join('、') || '', intent?.toneDirectives.join('、') || '', ] .filter(Boolean) .join(' / '); return [ buildAnchorLine('世界一句话', intent?.worldHook || ''), buildAnchorLine('玩家开局', playerOpeningText), buildAnchorLine('主题气质', themeToneText), buildAnchorLine('核心冲突', intent?.coreConflicts.join(';') || ''), buildAnchorLine('关键关系', relationshipText), buildAnchorLine('标志元素', intent?.iconicElements.join('、') || ''), ] .filter(Boolean) .join('\n'); } export function buildCustomWorldCreatorIntentDisplayText( intent: CustomWorldCreatorIntent | null | undefined, ) { if (!hasMeaningfulCustomWorldCreatorIntent(intent)) { return ''; } const lines = [ intent?.worldHook ? `世界一句话:${intent.worldHook}` : '', intent?.rawSettingText ? `补充设定:${intent.rawSettingText}` : '', buildAnchorLine('主题关键词', intent?.themeKeywords.join('、') || ''), buildAnchorLine('世界气质', intent?.toneDirectives.join('、') || ''), buildAnchorLine('玩家是谁', intent?.playerPremise || ''), buildAnchorLine('开局处境', intent?.openingSituation || ''), buildAnchorLine('核心冲突', intent?.coreConflicts.join('、') || ''), buildAnchorLine( '关键势力', intent?.keyFactions .map((entry) => [entry.name, entry.publicGoal, entry.tension] .filter(Boolean) .join(' / '), ) .filter(Boolean) .join(';') || '', ), buildAnchorLine( '关键角色', intent?.keyCharacters .map((entry) => [ entry.name, entry.role, entry.publicMask, entry.hiddenHook ? `暗线 ${entry.hiddenHook}` : '', ] .filter(Boolean) .join(' / '), ) .filter(Boolean) .join(';') || '', ), buildAnchorLine( '关键地点', intent?.keyLandmarks .map((entry) => [entry.name, entry.purpose, entry.mood].filter(Boolean).join(' / '), ) .filter(Boolean) .join(';') || '', ), buildAnchorLine('标志性要素', intent?.iconicElements.join('、') || ''), buildAnchorLine('禁止事项', intent?.forbiddenDirectives.join('、') || ''), ].filter(Boolean); return lines.join('\n'); } export function buildCustomWorldCreatorIntentGenerationText( intent: CustomWorldCreatorIntent | null | undefined, ) { if (!hasMeaningfulCustomWorldCreatorIntent(intent)) { return ''; } const sections = [ buildAnchorLine('世界核心命题', intent?.worldHook || ''), buildAnchorLine('补充设定原文', intent?.rawSettingText || ''), buildAnchorLine('主题关键词', intent?.themeKeywords.join('、') || ''), buildAnchorLine('情绪与气质', intent?.toneDirectives.join('、') || ''), buildAnchorLine('玩家身份', intent?.playerPremise || ''), buildAnchorLine('开局处境', intent?.openingSituation || ''), buildAnchorLine('核心冲突', intent?.coreConflicts.join('、') || ''), buildAnchorLine( '关键势力锚点', intent?.keyFactions .map((entry) => [ entry.name, entry.publicGoal ? `目标 ${entry.publicGoal}` : '', entry.tension ? `张力 ${entry.tension}` : '', entry.notes ? `补充 ${entry.notes}` : '', ] .filter(Boolean) .join(';'), ) .filter(Boolean) .join('\n') || '', ), buildAnchorLine( '关键角色锚点', intent?.keyCharacters .map((entry) => [ entry.name, entry.role ? `身份 ${entry.role}` : '', entry.publicMask ? `表面 ${entry.publicMask}` : '', entry.hiddenHook ? `暗线 ${entry.hiddenHook}` : '', entry.relationToPlayer ? `与玩家 ${entry.relationToPlayer}` : '', entry.notes ? `补充 ${entry.notes}` : '', ] .filter(Boolean) .join(';'), ) .filter(Boolean) .join('\n') || '', ), buildAnchorLine( '关键地点锚点', intent?.keyLandmarks .map((entry) => [ entry.name, entry.purpose ? `作用 ${entry.purpose}` : '', entry.mood ? `氛围 ${entry.mood}` : '', entry.secret ? `秘密 ${entry.secret}` : '', ] .filter(Boolean) .join(';'), ) .filter(Boolean) .join('\n') || '', ), buildAnchorLine('标志性要素', intent?.iconicElements.join('、') || ''), buildAnchorLine('禁止事项', intent?.forbiddenDirectives.join('、') || ''), ].filter(Boolean); return sections.join('\n\n'); } function buildCharacterAnchorSummary(entry: CreatorCharacterSeed): ActorAnchor { const summary = clampText( [ entry.role, entry.publicMask, entry.hiddenHook ? `暗线 ${entry.hiddenHook}` : '', entry.relationToPlayer ? `与玩家 ${entry.relationToPlayer}` : '', ] .filter(Boolean) .join(';'), 72, ); return { id: entry.id, name: entry.name || '未命名关键角色', summary, }; } function buildLandmarkAnchorSummary( entry: CreatorLandmarkSeed, ): LandmarkAnchor { const summary = clampText( [entry.purpose, entry.mood, entry.secret ? `秘密 ${entry.secret}` : ''] .filter(Boolean) .join(';'), 72, ); return { id: entry.id, name: entry.name || '未命名关键地点', summary, }; } export function buildCustomWorldAnchorPackFromIntent( intent: CustomWorldCreatorIntent | null | undefined, ): CustomWorldAnchorPack | null { if (!hasMeaningfulCustomWorldCreatorIntent(intent)) { return null; } const lockedAnchorIds = [ ...(intent?.keyCharacters .filter((entry) => entry.locked) .map((entry) => entry.id) ?? []), ...(intent?.keyLandmarks .filter((entry) => entry.locked) .map((entry) => entry.id) ?? []), ...(intent?.keyFactions .filter((entry) => entry.locked) .map((entry) => entry.id) ?? []), ]; return { worldSummary: clampText( intent?.worldHook || intent?.rawSettingText || '', 96, ), creatorIntentSummary: clampText( buildCustomWorldCreatorIntentDisplayText(intent), 240, ), lockedAnchorIds, keyConflictSummaries: intent?.coreConflicts.map((entry) => clampText(entry, 48)) ?? [], keyFactionSummaries: intent?.keyFactions.map((entry) => clampText( [entry.name, entry.publicGoal, entry.tension] .filter(Boolean) .join(';'), 72, ), ) ?? [], keyCharacterAnchors: intent?.keyCharacters.map((entry) => buildCharacterAnchorSummary(entry), ) ?? [], keyLandmarkAnchors: intent?.keyLandmarks.map((entry) => buildLandmarkAnchorSummary(entry)) ?? [], motifDirectives: [ ...(intent?.themeKeywords ?? []), ...(intent?.toneDirectives ?? []), ...(intent?.iconicElements ?? []), ].slice(0, 12), }; }