import type { CustomWorldAgentActionRequest, CustomWorldAgentOperationRecord, CustomWorldSupportedAction, } from '../../../packages/shared/src/contracts/customWorldAgent.js'; import { badRequest } from '../errors.js'; import { normalizeCustomWorldProfile } from '../modules/custom-world/runtimeProfile.js'; import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js'; import type { CustomWorldAgentActionExecutorMap, CustomWorldAgentActionPayload, } from './customWorldAgentActionExecutors/index.js'; import type { CustomWorldAgentSessionRecord } from './customWorldAgentSessionStore.js'; type EnabledAction = keyof CustomWorldAgentActionExecutorMap; type EnabledDescriptor = { operationType: CustomWorldAgentOperationRecord['type']; normalizePayload?: ( payload: CustomWorldAgentActionPayload, ) => CustomWorldAgentActionPayload; validate?: ( session: CustomWorldAgentSessionRecord, payload: CustomWorldAgentActionPayload, ) => void; execute: CustomWorldAgentActionExecutorMap[K]; }; type DisabledAction = Exclude; type DisabledDescriptor = { disabledReason: string; }; type ActionCapabilityState = { enabled: boolean; reason?: string; }; function assertDraftRefiningActionAvailable( session: CustomWorldAgentSessionRecord, action: string, ) { if ( session.stage !== 'object_refining' && session.stage !== 'visual_refining' ) { throw badRequest( `${action} is only available during object_refining or visual_refining`, ); } const hasDraftFoundation = Boolean( normalizeFoundationDraftProfile(session.draftProfile) && session.draftCards.length > 0, ); if (!hasDraftFoundation) { throw badRequest(`${action} requires an existing draft foundation`); } } function assertLongTailActionAvailable( session: CustomWorldAgentSessionRecord, action: string, ) { if ( session.stage !== 'object_refining' && session.stage !== 'visual_refining' && session.stage !== 'long_tail_review' && session.stage !== 'ready_to_publish' ) { throw badRequest( `${action} is only available during object_refining, visual_refining, long_tail_review or ready_to_publish`, ); } } function assertPublishActionAvailable( session: CustomWorldAgentSessionRecord, action: string, ) { assertLongTailActionAvailable(session, action); if (!normalizeFoundationDraftProfile(session.draftProfile)) { throw badRequest(`${action} requires an existing draft foundation`); } } export type PreparedCustomWorldAgentActionExecution = { operationType: CustomWorldAgentOperationRecord['type']; execute: (params: { userId: string; sessionId: string; operationId: string; }) => Promise; }; export class CustomWorldAgentActionRegistry { private readonly descriptors: Record< CustomWorldAgentActionRequest['action'], EnabledDescriptor | DisabledDescriptor >; constructor(executors: CustomWorldAgentActionExecutorMap) { this.descriptors = { draft_foundation: { operationType: 'draft_foundation', validate: (session) => { if (session.progressPercent < 100) { throw badRequest('draft_foundation requires progressPercent >= 100'); } }, execute: executors.draft_foundation, }, update_draft_card: { operationType: 'update_draft_card', validate: (session, payload) => { assertDraftRefiningActionAvailable(session, payload.action); if (!payload.cardId.trim()) { throw badRequest('update_draft_card requires cardId'); } if (!Array.isArray(payload.sections) || payload.sections.length === 0) { throw badRequest('update_draft_card requires sections'); } }, execute: executors.update_draft_card, }, sync_result_profile: { operationType: 'sync_result_profile', normalizePayload: (payload) => { const normalizedProfile = normalizeCustomWorldProfile(payload.profile, ''); if (!normalizedProfile) { throw badRequest('sync_result_profile requires a valid profile'); } return { ...payload, profile: normalizedProfile as unknown as Record, }; }, validate: (session, payload) => { assertDraftRefiningActionAvailable(session, payload.action); }, execute: executors.sync_result_profile, }, generate_characters: { operationType: 'generate_characters', validate: (session, payload) => { assertDraftRefiningActionAvailable(session, payload.action); if (payload.count < 1 || payload.count > 3) { throw badRequest( 'generate_characters count must be between 1 and 3', ); } }, execute: executors.generate_characters, }, generate_landmarks: { operationType: 'generate_landmarks', validate: (session, payload) => { assertDraftRefiningActionAvailable(session, payload.action); if (payload.count < 1 || payload.count > 3) { throw badRequest( 'generate_landmarks count must be between 1 and 3', ); } }, execute: executors.generate_landmarks, }, generate_role_assets: { operationType: 'generate_role_assets', validate: (session, payload) => { assertDraftRefiningActionAvailable(session, payload.action); if (!Array.isArray(payload.roleIds) || payload.roleIds.length !== 1) { throw badRequest( 'generate_role_assets currently requires exactly one roleId', ); } }, execute: executors.generate_role_assets, }, sync_role_assets: { operationType: 'sync_role_assets', validate: (session, payload) => { assertDraftRefiningActionAvailable(session, payload.action); if (!payload.roleId.trim()) { throw badRequest('sync_role_assets requires roleId'); } if ( !payload.portraitPath.trim() || !payload.generatedVisualAssetId.trim() ) { throw badRequest( 'sync_role_assets requires portraitPath and generatedVisualAssetId', ); } }, execute: executors.sync_role_assets, }, generate_scene_assets: { operationType: 'generate_scene_assets', validate: (session, payload) => { assertDraftRefiningActionAvailable(session, payload.action); if (!Array.isArray(payload.sceneIds) || payload.sceneIds.length !== 1) { throw badRequest( 'generate_scene_assets currently requires exactly one sceneId', ); } }, execute: executors.generate_scene_assets, }, sync_scene_assets: { operationType: 'sync_scene_assets', validate: (session, payload) => { assertDraftRefiningActionAvailable(session, payload.action); if (!payload.sceneId.trim()) { throw badRequest('sync_scene_assets requires sceneId'); } if (!payload.imageSrc.trim() || !payload.generatedSceneAssetId.trim()) { throw badRequest( 'sync_scene_assets requires imageSrc and generatedSceneAssetId', ); } }, execute: executors.sync_scene_assets, }, expand_long_tail: { operationType: 'expand_long_tail', validate: (session, payload) => { assertLongTailActionAvailable(session, payload.action); if (!normalizeFoundationDraftProfile(session.draftProfile)) { throw badRequest('expand_long_tail requires an existing draft foundation'); } }, execute: executors.expand_long_tail, }, publish_world: { operationType: 'publish_world', validate: (session, payload) => { assertPublishActionAvailable(session, payload.action); }, execute: executors.publish_world, }, revert_checkpoint: { operationType: 'revert_checkpoint', validate: (session, payload) => { assertLongTailActionAvailable(session, payload.action); if (!payload.checkpointId.trim()) { throw badRequest('revert_checkpoint requires checkpointId'); } const checkpoint = session.checkpoints.find( (entry) => entry.checkpointId === payload.checkpointId, ); if (!checkpoint) { throw badRequest('revert_checkpoint target checkpoint does not exist'); } if (!checkpoint.snapshot) { throw badRequest( 'revert_checkpoint target checkpoint does not contain a restorable snapshot', ); } }, execute: executors.revert_checkpoint, }, }; } // orchestrator 只关心“拿到一个已校验的动作执行计划”,不再自己维护所有 action 分支。 prepareExecution( session: CustomWorldAgentSessionRecord, payload: CustomWorldAgentActionRequest, ): PreparedCustomWorldAgentActionExecution { const descriptor = this.descriptors[payload.action]; if ('disabledReason' in descriptor) { throw badRequest(descriptor.disabledReason); } const normalizedPayload = descriptor.normalizePayload ? descriptor.normalizePayload(payload as never) : payload; descriptor.validate?.(session, normalizedPayload as never); return { operationType: descriptor.operationType, execute: ({ userId, sessionId, operationId }) => descriptor.execute({ userId, sessionId, operationId, payload: normalizedPayload as never, }), }; } buildSupportedActions( session: CustomWorldAgentSessionRecord, ): CustomWorldSupportedAction[] { return ( Object.entries(this.descriptors) as Array< [ CustomWorldAgentActionRequest['action'], EnabledDescriptor | DisabledDescriptor, ] > ).map(([action, descriptor]) => { const capability = this.resolveCapabilityState(session, action, descriptor); return { action, enabled: capability.enabled, reason: capability.reason ?? null, } satisfies CustomWorldSupportedAction; }); } private resolveCapabilityState( session: CustomWorldAgentSessionRecord, action: CustomWorldAgentActionRequest['action'], descriptor: EnabledDescriptor | DisabledDescriptor, ): ActionCapabilityState { if ('disabledReason' in descriptor) { return { enabled: false, reason: descriptor.disabledReason, }; } if (action === 'draft_foundation') { return session.progressPercent >= 100 ? { enabled: true } : { enabled: false, reason: 'draft_foundation requires progressPercent >= 100', }; } if ( action === 'update_draft_card' || action === 'sync_result_profile' || action === 'generate_characters' || action === 'generate_landmarks' || action === 'generate_role_assets' || action === 'sync_role_assets' || action === 'generate_scene_assets' || action === 'sync_scene_assets' ) { try { assertDraftRefiningActionAvailable(session, action); return { enabled: true }; } catch (error) { return { enabled: false, reason: error instanceof Error ? error.message : 'action unavailable', }; } } if (action === 'expand_long_tail') { try { assertLongTailActionAvailable(session, action); return { enabled: true }; } catch (error) { return { enabled: false, reason: error instanceof Error ? error.message : 'action unavailable', }; } } if (action === 'publish_world') { try { assertPublishActionAvailable(session, action); return { enabled: true }; } catch (error) { return { enabled: false, reason: error instanceof Error ? error.message : 'action unavailable', }; } } if (action === 'revert_checkpoint') { const restorableCheckpoint = session.checkpoints.find( (entry) => Boolean(entry.snapshot), ); if (!restorableCheckpoint) { return { enabled: false, reason: 'revert_checkpoint requires at least one restorable checkpoint snapshot', }; } try { assertLongTailActionAvailable(session, action); return { enabled: true }; } catch (error) { return { enabled: false, reason: error instanceof Error ? error.message : 'action unavailable', }; } } return { enabled: true }; } }