Skip to content

V4 split/03 core session rewrite - #200

Draft
renal128 wants to merge 8 commits into
nextfrom
v4-split/03-core-session-rewrite
Draft

V4 split/03 core session rewrite#200
renal128 wants to merge 8 commits into
nextfrom
v4-split/03-core-session-rewrite

Conversation

@renal128

@renal128 renal128 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

No description provided.

renal128 added 8 commits July 3, 2026 12:11
…event types

- EventParser: drop internal_tentative_agent_response and
  asr_initiation_metadata parsing (server no longer needs them handled;
  now silently ignored alongside other known-but-unhandled event types
  instead of throwing), default expects_response to false when absent,
  and fall back to top-level code/message when client_error lacks an
  error_event sub-object.
- IncomingEvents: remove the now-dead TentativeAgentResponseEvent/
  ASRInitiationMetadataEvent types and .tentativeAgentResponse/
  .asrInitiationMetadata cases; ClientToolCallEvent.expectsResponse
  defaults to false.
- EndReason: drop .agentNotConnected (no longer distinguished from
  .remoteDisconnected).
- Conversation+Events.swift: minimal compatibility edit dropping the
  two switch cases for the removed IncomingEvent cases above (the rest
  of this file's rewrite lands in the core-session-rewrite PR, which
  also needs to land together with Message.isPartial -- adding that
  field while this file's message-store logic is still unmigrated was
  verified to cause runtime data corruption in unrelated codepaths, so
  it's deferred there instead of included here).

Note: EventSerializer.swift and OutgoingEvents.swift are NOT included
here despite being conceptually "event wire protocol" -- their target
versions require ConversationConfig.textOnly, DynamicVariableValue, and
ConversationError.invalidToolResult, which don't exist until the
core-session-rewrite PR. They land there instead.
…tives

Replaces the ConversationOptions-era config/error/state shape with the
v4 primitives that the rest of this session rewrite builds on:

- ConversationAuth (new) replaces ElevenLabsConfiguration's auth cases
  (publicAgent/conversationToken/signedWebSocketURL); the
  .customTokenProvider case and participantName/agentId fields have no
  replacement.
- ElevenLabsEndpoints (new) makes API/WebSocket endpoints configurable
  per-session instead of hardcoded (see ConnectionConstants removal).
- ConversationConfig absorbs ConversationOptions' non-callback fields:
  new LogLevel/DynamicVariableValue types, relayOnly: Bool (replacing
  the deleted LiveKitNetworkConfiguration's 3-way ICE strategy + custom
  ICE servers -- those are no longer configurable), agentJoinTimeout/
  conversationInitTimeout (replacing ConversationStartupConfiguration).
- ConversationCallbacks (new) holds every onXxx closure split out of
  ConversationOptions. Dropped with no replacement: onCanSendFeedbackChange,
  onSpeechActivity, onConversationMetadata (now a published property
  instead), onAgentStateChange/onStartupStateChange (superseded by
  published agentState/state).
- ConversationError gains .initializationTimeout, .microphonePermissionDenied,
  .invalidToolResult, .invalidURL, and a recoverable .underlyingError;
  drops .localNetworkPermissionRequired (see LocalNetworkPermissionMonitor
  removal) and .noSoftwareMuteHandlerConfigured.
- ConversationState replaces .active(CallInfo) with .connected (the
  connected agent id is no longer retrievable from state at all -- CallInfo
  is deleted), .error(ConversationError) with .startupFailed(...), and adds
  StartupPhase for the new .connecting(phase:) case (no more startup-timing
  telemetry -- ConversationAgentReadyReport/StartupMetrics/StartupState/
  StartupResult are all deleted).
- AgentState is un-nested from ElevenLabs.AgentState to a top-level type
  (old nested one stays for now, removed with ElevenLabs.swift later in
  this stack).
…dpoints

TokenService.fetchConnectionDetails(configuration:) -> ConnectionDetails
(serverUrl/roomName/participantToken bundle) becomes
fetchRoomToken(auth:apiBase:environment:) -> String -- WebRTCConnectionManager
now owns constructing the room connection from that token plus
ConversationConfig.endpoints instead of TokenService handing back a
ready-made LiveKit connection bundle. The .customTokenProvider auth
case has no replacement (matches ConversationAuth). ConnectionConstants'
three hardcoded URLs are deleted now that endpoints are configurable
per-session via ElevenLabsEndpoints.
…ig/Auth

- ConnectionManaging: connect(auth:config:) throws unifies what was a
  differently-shaped connect() per concrete manager; onStartupPhaseChange
  replaces errorHandler (typed ConversationStartupFailure throws instead
  of a generic error callback).
- WebRTCConnectionManager: mic-permission request now runs concurrently
  with the token fetch (was sequential); readiness gate races
  LiveKitReadinessDelegate.awaitRemoteParticipant() (first remote
  participant joins) instead of waiting for the agent's audio track to
  subscribe -- this specific change already shipped separately as #197,
  landing here only because the v4 import was authored before that merged.
  Startup timing is now logged, not returned to the caller. Also drops
  the LocalNetworkPermissionMonitor-based "check Local Network permission"
  diagnostic with nothing replacing it.
- Dependencies: init(logLevel:) replaces a parameterless init;
  TokenService is now constructed with no endpoint config baked in.
  Adds an opt-in LogLevel.debugWithRTC tier that forwards LiveKit/WebRTC
  internal logs.
- SDKLogger: default level changes from .info to .warning; logLevel
  param renamed to levelOverride.
- LiveKitReadinessDelegate (in WebRTCConnectionManager.swift) changed
  from private back to internal so LiveKitReadinessDelegateTests (added
  by #197) can still construct it via @testable import; its call site
  there is updated for SDKLogger's renamed parameter.
…estore

- AudioPipelineConfiguration: MicrophoneMuteMode is no longer a LiveKit
  re-export -- it's the SDK's own 4-case enum (.inputMixer/.restart/
  .voiceProcessing/.software(speechThreshold:)), folding in what used to
  be 4 separate fields (microphoneMuteMode/useSoftwareMute/
  mutedSpeechThreshold + the onSpeechActivity/onMutedSpeech callback
  split). recordingAlwaysPrepared opt-out is removed -- engine pre-warm
  is now unconditional. LiveKit's SpeechActivityEvent no longer leaks
  into this public config surface.
- SoftwareMuteProcessor: onMutedSpeech(MutedSpeechEvent) (throttled,
  periodic, level-based) becomes onSpeakingWhileMutedChange(Bool) (fires
  once on the started/ended edge of a hangover-latched speech segment,
  including on unmute mid-segment). mutedSpeechThrottleInSeconds is gone.
- ConversationAudioManager: configure(with:) now snapshots and restores
  AudioManager.shared's capturePostProcessingDelegate/
  isVoiceProcessingBypassed/isVoiceProcessingAGCEnabled around this
  instance's lifetime (guarded by an identity check on restore) instead
  of unconditionally clearing them on cleanup -- fixes clobbering another
  component that took over the process-wide slot. setRecordingAlwaysPreparedMode
  is now awaited inside configure() (called before WebRTCConnectionManager.connect)
  instead of fired in the background at init -- this adds a new async hop
  to the front of the startup critical path; worth a real-device timing
  check given this SDK's startup-latency history, but not blocking this PR.
…alization

- Message.isPartial (true while an agent message is streaming in from
  agent_chat_response_part chunks, or a user transcript is tentative;
  false once finalized or for locally-appended messages) -- lands here,
  not with the rest of the event-model changes, because adding the field
  while this file's message-store logic was still unmigrated was
  verified to cause runtime data corruption in unrelated codepaths
  (a sendToolResult call started publishing garbage that looked like an
  unrelated feedback event) -- a partial-migration hazard specific to
  that combination, not present in either the fully-old or fully-new state.
- Conversation+Events.swift: insertUserTranscript/upsertAgentMessage
  replaced by applyUserTranscript/applyTentativeUserTranscript/
  applyAgentResponse/applyAgentResponsePart, backed by shared
  messageIndex/isNewerThanHighestEventId/appendMessage helpers --
  guards against reopening a finalized message or applying a stale/
  out-of-order part, which the old version didn't have. Ping replies
  now fire via a detached Task instead of being awaited inline, so a
  slow publish doesn't stall the serialized event-handling queue.
  callbacks.onPing added.
  Two behavior changes worth explicit reviewer sign-off, not mentioned
  in the v4 import's stated deviations: the .agentToolResponse handler
  for toolName == "end_call" no longer calls endConversation() (grep
  confirms nothing else does either -- server-driven auto-hangup appears
  gone), and onCanSendFeedbackChange/lastFeedbackSubmittedEventId
  tracking is removed entirely with no replacement.
- EventSerializer/OutgoingEvents: config.textOnly (flattened from
  conversationOverrides.textOnly) and DynamicVariableValue-based
  dynamic_variables serialization (was String-only) now that
  ConversationConfig carries them. ClientToolResultEvent's Any-based
  init is no longer deprecated (the Encodable overload it pointed
  reviewers to is gone -- see ConversationClient in the next commit)
  and throws ConversationError.invalidToolResult instead of silently
  stringifying an un-encodable result.
- ConversationClient (new, ObservableObject) replaces the static
  ElevenLabs.startConversation(...) factories and process-wide
  ElevenLabs.configure(_:)/ElevenLabsConfiguration -- it's a reusable,
  long-lived object (init(callbacks:), start(auth:config:) async throws,
  endConversation(), reset()) instead of a one-shot factory returning a
  Conversation. ElevenLabs.version (public) has no replacement --
  SDKVersion.version is internal-only now.
- Conversation is no longer public -- it's an internal session type
  ConversationClient owns per start() call. Its new AsyncStream-serialized
  event pipeline (eventStreamContinuation/eventConsumerTask) replaces
  direct closure dispatch from the connection manager. toggleMute/
  setMuted/isMuted are renamed toggleMicMute/setMicMuted/isMicMuted, and
  gain a symmetric toggleAgentMute/setAgentMuted/isAgentMuted; calling
  the mic-mute methods while disconnected is now a lenient no-op instead
  of throwing .notConnected. inputTrack/agentAudioTrack (raw LiveKit
  track access) and audioDevices/selectedAudioDeviceID (device
  enumeration) are removed with no replacement. sendToolResult's generic
  Encodable overload is gone -- only the Any-based overload remains, so
  a plain Encodable struct now throws ConversationError.invalidToolResult
  instead of JSON-encoding automatically.
- CallInfo (agentId-carrying struct) is deleted -- ConversationState no
  longer exposes the connected agent's id at all.
- Removes the dead onRawMessage plumbing that shipped with this v4 import:
  both connection managers declared it and Conversation.messageSource(for:)
  existed to feed it, but nothing ever wired connectionManager.onRawMessage
  to actually fire, so the documented public ConversationCallbacks.onMessage
  callback could never fire either. Removed both rather than ship a
  callback that silently never calls back; confirmed via a test
  (testRawMessageCallbackReceivesSourceAndRawJSON) that failed exactly
  this way before the fix.
- Test updates are almost entirely mechanical renames to match the above
  (ConversationOptions -> ConversationCallbacks/ConversationConfig split,
  .active(CallInfo) -> .connected, ElevenLabs.* -> ConversationClient/
  ConversationAuth/ElevenLabsEndpoints), plus one real fix: the hardcoded
  SDK-version string in ElevenLabsSDKTests was still "3.2.0", stale
  against the intentional 3.2.2 bump already on this branch.
  ConversationConfigTests/EventSerializerTests/ConversationEventHandlerTests
  gained real new coverage for the config/event changes earlier in this
  stack (see their respective commits). Coverage lost with no direct
  replacement: testSendToolResultEncodesEncodableResult (tracks the
  Encodable-overload removal above) and
  testTextOnlyStartDisconnectsPreviousActiveManagerBeforeSwitchingTransports
  (WebRTC->WebSocket teardown-before-switch ordering).

Full suite: 154 tests, 0 failures.
@renal128
renal128 force-pushed the v4-split/03-core-session-rewrite branch from f7cadfc to c4c7881 Compare July 3, 2026 13:08
Base automatically changed from v4-split/02-event-wire-protocol to next July 6, 2026 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant