Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions livekit-agents/livekit/agents/voice/agent_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,14 @@ def turn_detection(self) -> TurnDetectionMode | None:
def mcp_servers(self) -> list[mcp.MCPServer] | None:
return self._mcp_servers

@property
def recording_options(self) -> RecordingOptions:
"""The recording options currently in effect for this session.

Returns a copy; use :meth:`update_options` with ``record=`` to change them.
"""
return self._recording_options.copy()

@property
def input(self) -> io.AgentInput:
return self._input
Expand Down Expand Up @@ -1088,6 +1096,7 @@ def update_options(
*,
endpointing_opts: NotGivenOr[EndpointingOptions] = NOT_GIVEN,
turn_detection: NotGivenOr[TurnDetectionMode | None] = NOT_GIVEN,
record: NotGivenOr[bool | RecordingOptions] = NOT_GIVEN,
# deprecated
min_endpointing_delay: NotGivenOr[float] = NOT_GIVEN,
max_endpointing_delay: NotGivenOr[float] = NOT_GIVEN,
Expand All @@ -1099,6 +1108,14 @@ def update_options(
endpointing_opts (NotGivenOr[EndpointingOptions], optional): Endpointing options.
turn_detection (NotGivenOr[TurnDetectionMode | None], optional): Strategy for deciding
when the user has finished speaking. ``None`` reverts to automatic selection.
record (NotGivenOr[bool | RecordingOptions], optional): Which recording features
(audio, traces, logs, transcript) are active. Useful for toggling recording
mid-session, for example after obtaining recording consent from the user.
``True`` enables every feature, ``False`` disables every feature, and a
:class:`RecordingOptions` mapping updates only the given keys, leaving the rest
unchanged. Session audio is only captured when audio recording was enabled at
:meth:`start`; enabling ``audio`` afterwards does not retroactively start the
audio recorder, while disabling always takes effect.
min_endpointing_delay: Deprecated, use ``endpointing_opts`` instead.
max_endpointing_delay: Deprecated, use ``endpointing_opts`` instead.
"""
Expand Down Expand Up @@ -1130,6 +1147,18 @@ def update_options(
if is_given(turn_detection):
self._turn_detection = turn_detection

if is_given(record):
if isinstance(record, bool):
options = _resolve_recording_options(record)
else:
options = self._recording_options.copy()
options.update(record)

if self._text_only:
options["audio"] = False

self._recording_options = options

if self._activity is not None:
self._activity.update_options(
endpointing_opts=(
Expand Down
50 changes: 50 additions & 0 deletions tests/test_recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,56 @@ async def test_record_not_given_without_job_ctx() -> None:
await _cleanup(session)


async def test_update_options_record_bool() -> None:
"""update_options(record=bool) toggles every feature on/off."""
session = _create_simple_session()
await session.start(SimpleAgent(), record=False)
assert session._recording_options == _RECORDING_ALL_OFF

session.update_options(record=True)
assert session._recording_options == _RECORDING_ALL_ON
assert session.recording_options == _RECORDING_ALL_ON

session.update_options(record=False)
assert session._recording_options == _RECORDING_ALL_OFF
await _cleanup(session)


async def test_update_options_record_partial_merge() -> None:
"""A partial mapping updates only the given keys, leaving the rest unchanged."""
session = _create_simple_session()
await session.start(SimpleAgent(), record=True)
assert session._recording_options == _RECORDING_ALL_ON

session.update_options(record={"audio": False})
assert session._recording_options == {
"audio": False,
"traces": True,
"logs": True,
"transcript": True,
}

session.update_options(record={"transcript": False})
assert session._recording_options == {
"audio": False,
"traces": True,
"logs": True,
"transcript": False,
}
await _cleanup(session)


async def test_recording_options_property_returns_copy() -> None:
"""The recording_options property returns a copy that cannot mutate session state."""
session = _create_simple_session()
await session.start(SimpleAgent(), record=True)

opts = session.recording_options
opts["audio"] = False
assert session._recording_options["audio"] is True
await _cleanup(session)


# ---------------------------------------------------------------------------
# Group 2: init_recording() interaction with mock JobContext
# ---------------------------------------------------------------------------
Expand Down
Loading