import Foundation public protocol AIService: Sendable { func generateSongProject(from request: SongProjectGenerationRequest) async throws -> SongProjectGenerationResult func reviseLyrics(from request: LyricsRevisionRequest) async throws -> LyricsRevisionResult func proposeProjectUpdate(from request: SongProjectUpdateRequest) async throws -> SongProjectUpdateResult } public struct AIConversationMessage: Equatable, Identifiable, Sendable { public let id: String public var role: AIConversationRole public var content: String public init( id: String = UUID().uuidString, role: AIConversationRole, content: String ) { self.id = id self.role = role self.content = content } } public enum AIConversationRole: String, Equatable, Sendable { case user case assistant case system } public struct AIRequestContext: Equatable, Sendable { public var userInstruction: String public var conversation: [AIConversationMessage] public var localeIdentifier: String? internal var privateApplicationRules: PrivateApplicationRules? public init( userInstruction: String, conversation: [AIConversationMessage] = [], localeIdentifier: String? = nil ) { self.userInstruction = userInstruction self.conversation = conversation self.localeIdentifier = localeIdentifier self.privateApplicationRules = nil } internal mutating func injectPrivateApplicationRules(_ rules: PrivateApplicationRules?) { privateApplicationRules = rules } } public struct SongProjectGenerationRequest: Equatable, Sendable { public var context: AIRequestContext public var seedProject: SongProject? public var discussionMode: ConversationMode public init( context: AIRequestContext, seedProject: SongProject? = nil, discussionMode: ConversationMode = .auto ) { self.context = context self.seedProject = seedProject self.discussionMode = discussionMode } } public struct SongProjectGenerationResult: Equatable, Sendable { public var project: SongProject public var notes: [String] public var followUpQuestions: [String] public init( project: SongProject, notes: [String] = [], followUpQuestions: [String] = [] ) { self.project = project self.notes = notes self.followUpQuestions = followUpQuestions } } public struct LyricsRevisionRequest: Equatable, Sendable { public var context: AIRequestContext public var project: SongProject public var sourceLyrics: Lyrics public var mode: LyricsRevisionMode public init( context: AIRequestContext, project: SongProject, sourceLyrics: Lyrics, mode: LyricsRevisionMode ) { self.context = context self.project = project self.sourceLyrics = sourceLyrics self.mode = mode } } public enum LyricsRevisionMode: String, Equatable, Sendable { case writeFromIdea case correct case improve case restructure case complete } public struct LyricsRevisionResult: Equatable, Sendable { public var lyrics: Lyrics public var notes: [String] public init(lyrics: Lyrics, notes: [String] = []) { self.lyrics = lyrics self.notes = notes } } public struct SongProjectUpdateRequest: Equatable, Sendable { public var context: AIRequestContext public var project: SongProject public var allowedScopes: [SongProjectUpdateScope] public init( context: AIRequestContext, project: SongProject, allowedScopes: [SongProjectUpdateScope] ) { self.context = context self.project = project self.allowedScopes = allowedScopes } } public enum SongProjectUpdateScope: String, Codable, Equatable, Sendable { case duration case genres case instruments case vocals case languages case lyrics case structure case arrangement case musicalParameters case emotionalArc case productionDirections case sunoOutput } public struct SongProjectUpdateResult: Equatable, Sendable { public var project: SongProject public var changedScopes: [SongProjectUpdateScope] public var notes: [String] public var followUpQuestions: [String] public init( project: SongProject, changedScopes: [SongProjectUpdateScope] = [], notes: [String] = [], followUpQuestions: [String] = [] ) { self.project = project self.changedScopes = changedScopes self.notes = notes self.followUpQuestions = followUpQuestions } }