Skip to content

Commit 88d3e38

Browse files
renal128cursoragent
andcommitted
Pass environment on the public-agent handshake and conversation init.
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a72eef5 commit 88d3e38

8 files changed

Lines changed: 41 additions & 8 deletions

File tree

Sources/ElevenLabs/Internal/Authorization/TokenService.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ struct TokenService: Sendable {
4848
///
4949
/// Translates internal `TokenError`s into public `ConversationError`s so
5050
/// callers only ever deal with one error type.
51-
func fetchToken(for auth: ConversationAuth.Voice) async throws -> String {
51+
func fetchToken(for auth: ConversationAuth.Voice, environment: String?) async throws -> String {
5252
do {
5353
switch auth {
5454
case let .publicAgent(agentId):
55-
return try await fetchTokenFromAPI(agentId: agentId)
55+
return try await fetchTokenFromAPI(agentId: agentId, environment: environment)
5656
case let .conversationToken(mint):
5757
return try await mint()
5858
}

Sources/ElevenLabs/Internal/Authorization/TokenServicing.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22

33
protocol TokenServicing: Sendable {
44
/// Resolve the token a voice conversation authenticates with.
5-
func fetchToken(for auth: ConversationAuth.Voice) async throws -> String
5+
func fetchToken(for auth: ConversationAuth.Voice, environment: String?) async throws -> String
66
}
77

88
extension TokenService: TokenServicing {}

Sources/ElevenLabs/Internal/Networking/WebRTCConnectionManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ final class WebRTCConnectionManager: WebRTCConnectionManaging {
101101
let token = try await runPhase(
102102
timing: \.tokenFetch, metrics: &metrics, startTime: startTime
103103
) {
104-
try await tokenService.fetchToken(for: auth)
104+
try await tokenService.fetchToken(for: auth, environment: config.environment)
105105
}
106106

107107
// 2. Request microphone permission (denial doesn't block startup).

Sources/ElevenLabs/Internal/Networking/WebSocketConnectionManager.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ final class WebSocketConnectionManager: WebSocketConnectionManaging {
5151
do {
5252
resolved = try await Self.websocketUrl(
5353
for: auth,
54-
endpoints: endpoints
54+
endpoints: endpoints,
55+
environment: config.environment
5556
)
5657
} catch is CancellationError {
5758
throw CancellationError()
@@ -180,7 +181,8 @@ final class WebSocketConnectionManager: WebSocketConnectionManaging {
180181

181182
static func websocketUrl(
182183
for auth: ConversationAuth.TextOnly,
183-
endpoints: Endpoints
184+
endpoints: Endpoints,
185+
environment: String? = nil
184186
) async throws -> (url: URL, agentId: String) {
185187
switch auth {
186188
case let .publicAgent(agentId):
@@ -189,6 +191,9 @@ final class WebSocketConnectionManager: WebSocketConnectionManaging {
189191
}
190192
var queryItems = components.queryItems ?? []
191193
queryItems.append(URLQueryItem(name: "agent_id", value: agentId))
194+
if let environment {
195+
queryItems.append(URLQueryItem(name: "environment", value: environment))
196+
}
192197
components.queryItems = queryItems
193198
guard let url = components.url else {
194199
throw ConversationError.authenticationFailed("Invalid conversation URL")

Sources/ElevenLabs/Internal/Utilities/EventSerializer.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ enum EventSerializer {
128128
json["user_id"] = userId
129129
}
130130

131+
if let environment = config.environment {
132+
json["environment"] = environment
133+
}
134+
131135
return json
132136
}
133137
}

Sources/ElevenLabs/Public/Conversation/ConversationConfig.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ public struct ConversationConfig: Sendable {
88
public var customLlmExtraBody: [String: String]? // Simplified to be Sendable
99
public var dynamicVariables: [String: String]? // Simplified to be Sendable
1010
public var userId: String?
11-
/// Optional environment for the agent (defaults to production when nil)
11+
/// Workspace environment (`production` if nil). Applied to the public-agent
12+
/// handshake and conversation init.
1213
public var environment: String?
1314

1415
/// How to handle microphone setup failures during connection.

Tests/ElevenLabsTests/Mocks/MockWebSocketConnectionManager.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ final class MockWebSocketConnectionManager: WebSocketConnectionManaging {
3737
do {
3838
resolved = try await WebSocketConnectionManager.websocketUrl(
3939
for: auth,
40-
endpoints: config.endpoints
40+
endpoints: config.endpoints,
41+
environment: config.environment
4142
)
4243
lastConnectedURL = resolved.url
4344
} catch {

Tests/ElevenLabsTests/Unit/ElevenLabsSDKTests.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,28 @@ final class ElevenLabsSDKTests: XCTestCase {
5656
XCTAssertEqual(resolved.agentId, "agent-123")
5757
}
5858

59+
@MainActor
60+
func testWebsocketUrlAppendsPublicAgentEnvironment() async throws {
61+
let resolved = try await WebSocketConnectionManager.websocketUrl(
62+
for: .publicAgent(id: "agent-123"),
63+
endpoints: .production,
64+
environment: "staging"
65+
)
66+
XCTAssertEqual(
67+
resolved.url.absoluteString,
68+
"wss://api.elevenlabs.io/v1/convai/conversation?agent_id=agent-123&environment=staging"
69+
)
70+
}
71+
72+
func testConversationInitIncludesEnvironment() throws {
73+
let config = ConversationConfig(environment: "staging")
74+
let data = try EventSerializer.serializeOutgoingEvent(
75+
.conversationInit(ConversationInitEvent(config: config))
76+
)
77+
let json = try XCTUnwrap(JSONSerialization.jsonObject(with: data) as? [String: Any])
78+
XCTAssertEqual(json["environment"] as? String, "staging")
79+
}
80+
5981
func testConversationConfigDefaults() {
6082
let config = ConversationConfig()
6183

0 commit comments

Comments
 (0)