diff --git a/Sources/ElevenLabsSwift/ElevenLabsSwift.swift b/Sources/ElevenLabsSwift/ElevenLabsSwift.swift index 3a57131..fb0edcf 100644 --- a/Sources/ElevenLabsSwift/ElevenLabsSwift.swift +++ b/Sources/ElevenLabsSwift/ElevenLabsSwift.swift @@ -5,7 +5,7 @@ import os.log /// Main class for ElevenLabsSwift package public class ElevenLabsSDK { - public static let version = "1.0.0" + public static let version = "1.0.1" private enum Constants { static let defaultApiOrigin = "wss://api.elevenlabs.io" @@ -18,6 +18,80 @@ public class ElevenLabsSDK { static let bufferSize: AVAudioFrameCount = 1024 } + // MARK: - Session Config Utilities + + public enum Language: String, Codable, Sendable { + case en, ja, zh, de, hi, fr, ko, pt, it, es, id, nl, tr, pl, sv, bg, ro, ar, cs, el, fi, ms, da, ta, uk, ru, hu, no, vi + } + + public struct AgentPrompt: Codable, Sendable { + public var prompt: String? + + public init(prompt: String? = nil) { + self.prompt = prompt + } + } + + public struct TTSConfig: Codable, Sendable { + public var voiceId: String? + + private enum CodingKeys: String, CodingKey { + case voiceId = "voice_id" + } + + public init(voiceId: String? = nil) { + self.voiceId = voiceId + } + } + + public struct ConversationConfigOverride: Codable, Sendable { + public var agent: AgentConfig? + public var tts: TTSConfig? + + public init(agent: AgentConfig? = nil, tts: TTSConfig? = nil) { + self.agent = agent + self.tts = tts + } + } + + public struct AgentConfig: Codable, Sendable { + public var prompt: AgentPrompt? + public var firstMessage: String? + public var language: Language? + + private enum CodingKeys: String, CodingKey { + case prompt + case firstMessage = "first_message" + case language + } + + public init(prompt: AgentPrompt? = nil, firstMessage: String? = nil, language: Language? = nil) { + self.prompt = prompt + self.firstMessage = firstMessage + self.language = language + } + } + + public enum LlmExtraBodyValue: Codable, Sendable { + case string(String) + case number(Double) + case boolean(Bool) + case null + case array([LlmExtraBodyValue]) + case dictionary([String: LlmExtraBodyValue]) + + var jsonValue: Any { + switch self { + case let .string(str): return str + case let .number(num): return num + case let .boolean(bool): return bool + case .null: return NSNull() + case let .array(arr): return arr.map { $0.jsonValue } + case let .dictionary(dict): return dict.mapValues { $0.jsonValue } + } + } + } + // MARK: - Audio Utilities public static func arrayBufferToBase64(_ data: Data) -> String { @@ -113,15 +187,21 @@ public class ElevenLabsSDK { public struct SessionConfig: Sendable { public let signedUrl: String? public let agentId: String? + public let overrides: ConversationConfigOverride? + public let customLlmExtraBody: [String: LlmExtraBodyValue]? - public init(signedUrl: String) { + public init(signedUrl: String, overrides: ConversationConfigOverride? = nil, customLlmExtraBody: [String: LlmExtraBodyValue]? = nil) { self.signedUrl = signedUrl agentId = nil + self.overrides = overrides + self.customLlmExtraBody = customLlmExtraBody } - public init(agentId: String) { + public init(agentId: String, overrides: ConversationConfigOverride? = nil, customLlmExtraBody: [String: LlmExtraBodyValue]? = nil) { self.agentId = agentId signedUrl = nil + self.overrides = overrides + self.customLlmExtraBody = customLlmExtraBody } } @@ -157,6 +237,25 @@ public class ElevenLabsSDK { let socket = session.webSocketTask(with: url) socket.resume() + // Always send initialization event + var initEvent: [String: Any] = ["type": "conversation_initiation_client_data"] + + // Add overrides if present + if let overrides = config.overrides, + let overridesDict = overrides.dictionary + { + initEvent["conversation_config_override"] = overridesDict + } + + // Add custom body if present + if let customBody = config.customLlmExtraBody { + initEvent["custom_llm_extra_body"] = customBody.mapValues { $0.jsonValue } + } + + let jsonData = try JSONSerialization.data(withJSONObject: initEvent) + let jsonString = String(data: jsonData, encoding: .utf8)! + try await socket.send(.string(jsonString)) + let configData = try await receiveInitialMessage(socket: socket) return Connection(socket: socket, conversationId: configData.conversationId, sampleRate: configData.sampleRate) } @@ -1012,3 +1111,10 @@ private extension Data { self = buffer.withUnsafeBufferPointer { Data(buffer: $0) } } } + +extension Encodable { + var dictionary: [String: Any]? { + guard let data = try? JSONEncoder().encode(self) else { return nil } + return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)) as? [String: Any] + } +}