Skip to content

Commit 44e2af9

Browse files
committed
Cmts
1 parent a2654a0 commit 44e2af9

File tree

6 files changed

+118
-6
lines changed

6 files changed

+118
-6
lines changed

Sources/LiveKit/Agent/Agent.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@
1616

1717
import Foundation
1818

19+
/// Represents a LiveKit Agent.
20+
///
21+
/// The ``Agent`` struct represents the state of a LiveKit agent within a ``Session``.
22+
/// It provides information about the agent's connection status, its current state
23+
/// (e.g., listening, thinking, speaking), and its media tracks.
24+
///
25+
/// The ``Agent``'s properties are updated automatically by the ``Session`` as the agent's
26+
/// state changes. This allows the application to react to the agent's
27+
/// behavior, such as displaying its avatar video or indicating when it is speaking.
28+
/// The ``agentState`` property is particularly useful for building UIs that reflect
29+
/// the agent's current activity.
30+
///
31+
/// - SeeAlso: [LiveKit SwiftUI Agent Starter](https://github.com/livekit-examples/agent-starter-swift).
32+
/// - SeeAlso: [LiveKit Agents documentation](https://docs.livekit.io/agents/).
1933
public struct Agent: Loggable {
2034
// MARK: - Error
2135

@@ -25,7 +39,7 @@ public struct Agent: Loggable {
2539
public var errorDescription: String? {
2640
switch self {
2741
case .timeout:
28-
"Agent not connected"
42+
"Agent did not connect"
2943
}
3044
}
3145
}
@@ -44,7 +58,7 @@ public struct Agent: Loggable {
4458
// MARK: - Transitions
4559

4660
mutating func disconnected() {
47-
log("Agent disconnected from \(state)", .debug)
61+
log("Agent disconnected from \(state)")
4862
// From any state
4963
state = .disconnected
5064
}
@@ -89,34 +103,39 @@ public struct Agent: Loggable {
89103

90104
// MARK: - Public
91105

106+
/// A boolean value indicating whether the agent is connected.
92107
public var isConnected: Bool {
93108
switch state {
94109
case .connected: true
95110
default: false
96111
}
97112
}
98113

114+
/// The current conversational state of the agent.
99115
public var agentState: AgentState? {
100116
switch state {
101117
case let .connected(agentState, _, _): agentState
102118
default: nil
103119
}
104120
}
105121

122+
/// The agent's audio track.
106123
public var audioTrack: (any AudioTrack)? {
107124
switch state {
108125
case let .connected(_, audioTrack, _): audioTrack
109126
default: nil
110127
}
111128
}
112129

130+
/// The agent's avatar video track.
113131
public var avatarVideoTrack: (any VideoTrack)? {
114132
switch state {
115133
case let .connected(_, _, avatarVideoTrack): avatarVideoTrack
116134
default: nil
117135
}
118136
}
119137

138+
/// The last error that occurred.
120139
public var error: Error? {
121140
switch state {
122141
case let .failed(error): error

Sources/LiveKit/Agent/Chat/Receive/MessageReceiver.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import Foundation
2020
///
2121
/// A message receiver is responsible for creating a stream of messages from the agent.
2222
/// It is used to receive messages from the agent and update the message feed.
23-
///
24-
/// - SeeAlso: ``ReceivedMessage``
2523
public protocol MessageReceiver: Sendable {
2624
func messages() async throws -> AsyncStream<ReceivedMessage>
2725
}

Sources/LiveKit/Agent/Chat/Send/MessageSender.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import Foundation
2020
///
2121
/// A message sender is responsible for sending messages to the agent.
2222
/// It is used to send messages to the agent and update the message feed.
23-
///
24-
/// - SeeAlso: ``SentMessage``
2523
public protocol MessageSender: Sendable {
2624
func send(_ message: SentMessage) async throws
2725
}

Sources/LiveKit/Agent/Session.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,24 @@ import Combine
1818
import Foundation
1919
import OrderedCollections
2020

21+
/// A ``Session`` represents a connection to a LiveKit Room that can contain an ``Agent``.
22+
///
23+
/// ``Session`` is the main entry point for interacting with a LiveKit agent. It encapsulates
24+
/// the connection to a LiveKit ``Room``, manages the agent's lifecycle, and handles
25+
/// communication between the user and the agent.
26+
///
27+
/// ``Session`` is created with a token source and optional configuration. The ``start()``
28+
/// method establishes the connection, and the ``end()`` method terminates it. The session's
29+
/// state, including connection status and any errors, is published for observation,
30+
/// making it suitable for use in SwiftUI applications.
31+
///
32+
/// Communication with the agent is handled through messages. The ``send(text:)`` method
33+
/// sends a user message, and the ``messages`` property provides an ordered history of the
34+
/// conversation. The session can be configured with custom message senders and receivers
35+
/// to support different communication channels, such as text messages or transcription streams.
36+
///
37+
/// - SeeAlso: [LiveKit SwiftUI Agent Starter](https://github.com/livekit-examples/agent-starter-swift).
38+
/// - SeeAlso: [LiveKit Agents documentation](https://docs.livekit.io/agents/).
2139
@MainActor
2240
open class Session: ObservableObject {
2341
private static let agentNameAttribute = "lk.agent_name"
@@ -40,9 +58,12 @@ open class Session: ObservableObject {
4058

4159
// MARK: - Published
4260

61+
/// The last error that occurred.
4362
@Published public private(set) var error: Error?
4463

64+
/// The current connection state of the session.
4565
@Published public private(set) var connectionState: ConnectionState = .disconnected
66+
/// A boolean value indicating whether the session is connected.
4667
public var isConnected: Bool {
4768
switch connectionState {
4869
case .connecting, .connected:
@@ -52,13 +73,16 @@ open class Session: ObservableObject {
5273
}
5374
}
5475

76+
/// The ``Agent`` associated with this session.
5577
@Published public private(set) var agent = Agent()
5678

5779
@Published private var messagesDict: OrderedDictionary<ReceivedMessage.ID, ReceivedMessage> = [:]
80+
/// The ordered list of received messages.
5881
public var messages: [ReceivedMessage] { messagesDict.values.elements }
5982

6083
// MARK: - Dependencies
6184

85+
/// The underlying ``Room`` object for the session.
6286
public let room: Room
6387

6488
private enum TokenSourceConfiguration {
@@ -98,6 +122,12 @@ open class Session: ObservableObject {
98122
observe(receivers: resolvedReceivers)
99123
}
100124

125+
/// Initializes a new ``Session`` with a fixed token source.
126+
/// - Parameters:
127+
/// - tokenSource: A token source that provides a fixed token.
128+
/// - options: The session options.
129+
/// - senders: An array of message senders.
130+
/// - receivers: An array of message receivers.
101131
public convenience init(tokenSource: any TokenSourceFixed,
102132
options: SessionOptions = .init(),
103133
senders: [any MessageSender]? = nil,
@@ -109,6 +139,13 @@ open class Session: ObservableObject {
109139
receivers: receivers)
110140
}
111141

142+
/// Initializes a new ``Session`` with a configurable token source.
143+
/// - Parameters:
144+
/// - tokenSource: A token source that can generate tokens with specific options.
145+
/// - tokenOptions: The options for generating the token.
146+
/// - options: The session options.
147+
/// - senders: An array of message senders.
148+
/// - receivers: An array of message receivers.
112149
public convenience init(tokenSource: any TokenSourceConfigurable,
113150
tokenOptions: TokenRequestOptions = .init(),
114151
options: SessionOptions = .init(),
@@ -121,6 +158,15 @@ open class Session: ObservableObject {
121158
receivers: receivers)
122159
}
123160

161+
/// Creates a new ``Session`` configured for a specific agent.
162+
/// - Parameters:
163+
/// - agentName: The name of the agent to dispatch.
164+
/// - agentMetadata: Metadata passed to the agent.
165+
/// - tokenSource: A configurable token source.
166+
/// - options: The session options.
167+
/// - senders: An array of message senders.
168+
/// - receivers: An array of message receivers.
169+
/// - Returns: A new ``Session`` instance.
124170
public static func withAgent(_ agentName: String,
125171
agentMetadata: String? = nil,
126172
tokenSource: any TokenSourceConfigurable,
@@ -173,6 +219,7 @@ open class Session: ObservableObject {
173219

174220
// MARK: - Lifecycle
175221

222+
/// Starts the session.
176223
public func start() async {
177224
guard connectionState == .disconnected else { return }
178225

@@ -210,16 +257,21 @@ open class Session: ObservableObject {
210257
}
211258
}
212259

260+
/// Terminates the session.
213261
public func end() async {
214262
await room.disconnect()
215263
}
216264

265+
/// Resets the last error.
217266
public func resetError() {
218267
error = nil
219268
}
220269

221270
// MARK: - Messages
222271

272+
/// Sends a text message.
273+
/// - Parameter text: The text to send.
274+
/// - Returns: The ``SentMessage`` that was sent.
223275
@discardableResult
224276
public func send(text: String) async -> SentMessage {
225277
let message = SentMessage(id: UUID().uuidString, timestamp: Date(), content: .userInput(text))
@@ -233,10 +285,14 @@ open class Session: ObservableObject {
233285
return message
234286
}
235287

288+
/// Gets the message history.
289+
/// - Returns: An array of ``ReceivedMessage``.
236290
public func getMessageHistory() -> [ReceivedMessage] {
237291
messages
238292
}
239293

294+
/// Restores the message history.
295+
/// - Parameter messages: An array of ``ReceivedMessage`` to restore.
240296
public func restoreMessageHistory(_ messages: [ReceivedMessage]) {
241297
messagesDict = .init(uniqueKeysWithValues: messages.sorted(by: { $0.timestamp < $1.timestamp }).map { ($0.id, $0) })
242298
}

Sources/LiveKit/Agent/SessionOptions.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,17 @@
1616

1717
import Foundation
1818

19+
/// Options for creating a ``Session``.
1920
public struct SessionOptions: Sendable {
21+
/// The undelying ``Room`` object for the session.
2022
public var room: Room
23+
/// Whether to enable audio pre-connect with ``PreConnectAudioBuffer``.
24+
/// If enabled, the microphone will be enabled before connecting to the room.
25+
/// Use ``LocalMedia`` or ``AudioManager/setRecordingAlwaysPreparedMode(_:)``
26+
/// to request microphone permissions early in the app lifecycle.
2127
public var preConnectAudio: Bool
28+
/// The timeout for the agent to connect, in seconds.
29+
/// If exceeded, the ``Agent`` will transition to a failed state.
2230
public var agentConnectTimeout: TimeInterval
2331

2432
public init(

Sources/LiveKit/SwiftUI/LocalMedia.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
import Combine
1919
import Foundation
2020

21+
/// An ``ObservableObject`` that can be used to control the local participant's media devices.
22+
///
23+
/// This class provides a convenient way to manage local media tracks, including enabling/disabling
24+
/// microphone and camera, and selecting audio and video devices. It is designed to be used
25+
/// in SwiftUI views.
2126
@MainActor
2227
open class LocalMedia: ObservableObject {
2328
// MARK: - Error
@@ -35,22 +40,34 @@ open class LocalMedia: ObservableObject {
3540

3641
// MARK: - Devices
3742

43+
/// The last error that occurred.
3844
@Published public private(set) var error: Error?
3945

46+
/// The local microphone track.
4047
@Published public private(set) var microphoneTrack: (any AudioTrack)?
48+
/// The local camera track.
4149
@Published public private(set) var cameraTrack: (any VideoTrack)?
50+
/// The local screen share track.
4251
@Published public private(set) var screenShareTrack: (any VideoTrack)?
4352

53+
/// A boolean value indicating whether the microphone is enabled.
4454
@Published public private(set) var isMicrophoneEnabled: Bool = false
55+
/// A boolean value indicating whether the camera is enabled.
4556
@Published public private(set) var isCameraEnabled: Bool = false
57+
/// A boolean value indicating whether screen sharing is enabled.
4658
@Published public private(set) var isScreenShareEnabled: Bool = false
4759

60+
/// The available audio input devices.
4861
@Published public private(set) var audioDevices: [AudioDevice] = AudioManager.shared.inputDevices
62+
/// The ID of the selected audio input device.
4963
@Published public private(set) var selectedAudioDeviceID: String = AudioManager.shared.inputDevice.deviceId
5064

65+
/// The available video capture devices.
5166
@Published public private(set) var videoDevices: [AVCaptureDevice] = []
67+
/// The ID of the selected video capture device.
5268
@Published public private(set) var selectedVideoDeviceID: String?
5369

70+
/// A boolean value indicating whether the camera position can be switched.
5471
@Published public private(set) var canSwitchCamera = false
5572

5673
// MARK: - Dependencies
@@ -59,17 +76,23 @@ open class LocalMedia: ObservableObject {
5976

6077
// MARK: - Initialization
6178

79+
/// Initializes a new ``LocalMedia`` object.
80+
/// - Parameter localParticipant: The ``LocalParticipant`` to control.
6281
public init(localParticipant: LocalParticipant) {
6382
self.localParticipant = localParticipant
6483

6584
observe(localParticipant)
6685
observeDevices()
6786
}
6887

88+
/// Initializes a new ``LocalMedia`` object.
89+
/// - Parameter room: The ``Room`` to control.
6990
public convenience init(room: Room) {
7091
self.init(localParticipant: room.localParticipant)
7192
}
7293

94+
/// Initializes a new ``LocalMedia`` object.
95+
/// - Parameter session: The ``Session`` to control.
7396
public convenience init(session: Session) {
7497
self.init(room: session.room)
7598
}
@@ -116,6 +139,7 @@ open class LocalMedia: ObservableObject {
116139

117140
// MARK: - Toggle
118141

142+
/// Toggles the microphone on or off.
119143
public func toggleMicrophone() async {
120144
do {
121145
try await localParticipant.setMicrophone(enabled: !isMicrophoneEnabled)
@@ -124,6 +148,8 @@ open class LocalMedia: ObservableObject {
124148
}
125149
}
126150

151+
/// Toggles the camera on or off.
152+
/// - Parameter disableScreenShare: If `true`, screen sharing will be disabled when the camera is enabled.
127153
public func toggleCamera(disableScreenShare: Bool = false) async {
128154
let enable = !isCameraEnabled
129155
do {
@@ -138,6 +164,8 @@ open class LocalMedia: ObservableObject {
138164
}
139165
}
140166

167+
/// Toggles screen sharing on or off.
168+
/// - Parameter disableCamera: If `true`, the camera will be disabled when screen sharing is enabled.
141169
public func toggleScreenShare(disableCamera: Bool = false) async {
142170
let enable = !isScreenShareEnabled
143171
do {
@@ -152,13 +180,17 @@ open class LocalMedia: ObservableObject {
152180

153181
// MARK: - Select
154182

183+
/// Selects an audio input device.
184+
/// - Parameter audioDevice: The ``AudioDevice`` to select.
155185
public func select(audioDevice: AudioDevice) {
156186
selectedAudioDeviceID = audioDevice.deviceId
157187

158188
let device = AudioManager.shared.inputDevices.first(where: { $0.deviceId == selectedAudioDeviceID }) ?? AudioManager.shared.defaultInputDevice
159189
AudioManager.shared.inputDevice = device
160190
}
161191

192+
/// Selects a video capture device.
193+
/// - Parameter videoDevice: The ``AVCaptureDevice`` to select.
162194
public func select(videoDevice: AVCaptureDevice) async {
163195
selectedVideoDeviceID = videoDevice.uniqueID
164196

@@ -167,6 +199,7 @@ open class LocalMedia: ObservableObject {
167199
_ = try? await cameraCapturer.set(options: captureOptions)
168200
}
169201

202+
/// Switches the camera position.
170203
public func switchCamera() async {
171204
guard let cameraCapturer = getCameraCapturer() else { return }
172205
_ = try? await cameraCapturer.switchCameraPosition()

0 commit comments

Comments
 (0)