Skip to content

Commit 52e0ffc

Browse files
committed
feat: add custom URLSessionFactory support for streaming requests
Expose URLSessionFactory protocol publicly to allow injecting custom session factories for streaming requests. This enables custom HTTP transport implementations to intercept streaming data. Changes: - Make URLSessionFactory, URLSessionDelegateProtocol, and URLSessionDataDelegateProtocol public - Add AnyObject constraint to delegate protocols for weak references - Update ImplicitURLSessionStreamingSessionFactory to accept custom factory - Add new OpenAI initializer with streamingURLSessionFactory parameter
1 parent 20779e1 commit 52e0ffc

4 files changed

Lines changed: 70 additions & 11 deletions

File tree

Sources/OpenAI/OpenAI.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ final public class OpenAI: OpenAIProtocol, @unchecked Sendable {
107107
}
108108

109109
/// Creates an OpenAI client with a custom URLSession protocol implementation.
110-
/// Use this initializer to inject a custom HTTP transport for encryption or other purposes.
110+
///
111+
/// - Important: This initializer only uses the custom session for non-streaming requests.
112+
/// For streaming requests, use the initializer that accepts a `URLSessionFactory`.
111113
///
112114
/// - Parameters:
113115
/// - configuration: The client configuration
@@ -132,6 +134,34 @@ final public class OpenAI: OpenAIProtocol, @unchecked Sendable {
132134
)
133135
}
134136

137+
/// Creates an OpenAI client with custom session handling for both regular and streaming requests.
138+
///
139+
/// - Parameters:
140+
/// - configuration: The client configuration
141+
/// - customSession: Custom URLSession protocol implementation for non-streaming requests
142+
/// - streamingURLSessionFactory: Factory for creating sessions for streaming requests
143+
/// - middlewares: Optional middlewares for request/response interception
144+
public convenience init(
145+
configuration: Configuration,
146+
customSession: any URLSessionProtocol,
147+
streamingURLSessionFactory: URLSessionFactory,
148+
middlewares: [OpenAIMiddleware] = []
149+
) {
150+
let streamingSessionFactory = ImplicitURLSessionStreamingSessionFactory(
151+
urlSessionFactory: streamingURLSessionFactory,
152+
middlewares: middlewares,
153+
parsingOptions: configuration.parsingOptions,
154+
sslDelegate: nil
155+
)
156+
157+
self.init(
158+
configuration: configuration,
159+
session: customSession,
160+
streamingSessionFactory: streamingSessionFactory,
161+
middlewares: middlewares
162+
)
163+
}
164+
135165
init(
136166
configuration: Configuration,
137167
session: URLSessionProtocol,

Sources/OpenAI/Private/Streaming/ServerSentEventsStreamingSessionFactory.swift

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,31 @@ protocol StreamingSessionFactory: Sendable {
3535
}
3636

3737
struct ImplicitURLSessionStreamingSessionFactory: StreamingSessionFactory {
38+
let urlSessionFactory: URLSessionFactory
3839
let middlewares: [OpenAIMiddleware]
3940
let parsingOptions: ParsingOptions
4041
let sslDelegate: SSLDelegateProtocol?
41-
42+
43+
init(
44+
urlSessionFactory: URLSessionFactory = FoundationURLSessionFactory(),
45+
middlewares: [OpenAIMiddleware],
46+
parsingOptions: ParsingOptions,
47+
sslDelegate: SSLDelegateProtocol?
48+
) {
49+
self.urlSessionFactory = urlSessionFactory
50+
self.middlewares = middlewares
51+
self.parsingOptions = parsingOptions
52+
self.sslDelegate = sslDelegate
53+
}
54+
4255
func makeServerSentEventsStreamingSession<ResultType>(
4356
urlRequest: URLRequest,
4457
onReceiveContent: @Sendable @escaping (StreamingSession<ServerSentEventsStreamInterpreter<ResultType>>, ResultType) -> Void,
4558
onProcessingError: @Sendable @escaping (StreamingSession<ServerSentEventsStreamInterpreter<ResultType>>, any Error) -> Void,
4659
onComplete: @Sendable @escaping (StreamingSession<ServerSentEventsStreamInterpreter<ResultType>>, (any Error)?) -> Void
4760
) -> StreamingSession<ServerSentEventsStreamInterpreter<ResultType>> where ResultType : Decodable, ResultType : Encodable, ResultType : Sendable {
4861
.init(
62+
urlSessionFactory: urlSessionFactory,
4963
urlRequest: urlRequest,
5064
interpreter: .init(parsingOptions: parsingOptions),
5165
sslDelegate: sslDelegate,
@@ -55,14 +69,15 @@ struct ImplicitURLSessionStreamingSessionFactory: StreamingSessionFactory {
5569
onComplete: onComplete
5670
)
5771
}
58-
72+
5973
func makeAudioSpeechStreamingSession(
6074
urlRequest: URLRequest,
6175
onReceiveContent: @Sendable @escaping (StreamingSession<AudioSpeechStreamInterpreter>, AudioSpeechResult) -> Void,
6276
onProcessingError: @Sendable @escaping (StreamingSession<AudioSpeechStreamInterpreter>, any Error) -> Void,
6377
onComplete: @Sendable @escaping (StreamingSession<AudioSpeechStreamInterpreter>, (any Error)?) -> Void
6478
) -> StreamingSession<AudioSpeechStreamInterpreter> {
6579
.init(
80+
urlSessionFactory: urlSessionFactory,
6681
urlRequest: urlRequest,
6782
interpreter: .init(),
6883
sslDelegate: sslDelegate,
@@ -72,14 +87,15 @@ struct ImplicitURLSessionStreamingSessionFactory: StreamingSessionFactory {
7287
onComplete: onComplete
7388
)
7489
}
75-
90+
7691
func makeModelResponseStreamingSession(
7792
urlRequest: URLRequest,
7893
onReceiveContent: @Sendable @escaping (StreamingSession<ModelResponseEventsStreamInterpreter>, ResponseStreamEvent) -> Void,
7994
onProcessingError: @Sendable @escaping (StreamingSession<ModelResponseEventsStreamInterpreter>, any Error) -> Void,
8095
onComplete: @Sendable @escaping (StreamingSession<ModelResponseEventsStreamInterpreter>, (any Error)?) -> Void
8196
) -> StreamingSession<ModelResponseEventsStreamInterpreter> {
8297
.init(
98+
urlSessionFactory: urlSessionFactory,
8399
urlRequest: urlRequest,
84100
interpreter: .init(),
85101
sslDelegate: sslDelegate,

Sources/OpenAI/Private/URLSessionDelegateProtocol.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,24 @@ import Foundation
1111
import FoundationNetworking
1212
#endif
1313

14-
protocol URLSessionDelegateProtocol: Sendable { // Sendable to make a better match with URLSessionDelegate, it's sendable too
14+
/// Protocol for handling URLSession delegate callbacks.
15+
/// Sendable to match URLSessionDelegate behavior.
16+
/// AnyObject constraint allows weak references to delegate implementations.
17+
public protocol URLSessionDelegateProtocol: AnyObject, Sendable {
1518
func urlSession(_ session: URLSessionProtocol, task: URLSessionTaskProtocol, didCompleteWithError error: Error?)
16-
19+
1720
func urlSession(
1821
_ session: URLSession,
1922
didReceive challenge: URLAuthenticationChallenge,
2023
completionHandler: @escaping @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
2124
)
2225
}
2326

24-
protocol URLSessionDataDelegateProtocol: URLSessionDelegateProtocol {
27+
/// Protocol for handling URLSession data delegate callbacks.
28+
/// Used for streaming data reception.
29+
public protocol URLSessionDataDelegateProtocol: URLSessionDelegateProtocol {
2530
func urlSession(_ session: URLSessionProtocol, dataTask: URLSessionDataTaskProtocol, didReceive data: Data)
26-
31+
2732
func urlSession(
2833
_ session: URLSessionProtocol,
2934
dataTask: URLSessionDataTaskProtocol,

Sources/OpenAI/Private/URLSessionFactory.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ import Foundation
1010
import FoundationNetworking
1111
#endif
1212

13-
protocol URLSessionFactory: Sendable {
13+
/// Factory protocol for creating URLSession instances.
14+
/// Implement this protocol to provide custom session creation for streaming requests.
15+
public protocol URLSessionFactory: Sendable {
16+
/// Creates a URLSession for streaming requests.
17+
/// - Parameter delegate: The delegate to receive streaming data callbacks
18+
/// - Returns: A URLSession protocol implementation
1419
func makeUrlSession(delegate: URLSessionDataDelegateProtocol) -> URLSessionProtocol
1520
}
1621

17-
struct FoundationURLSessionFactory: URLSessionFactory {
18-
func makeUrlSession(delegate: URLSessionDataDelegateProtocol) -> any URLSessionProtocol {
22+
/// Default factory that creates standard Foundation URLSession instances.
23+
public struct FoundationURLSessionFactory: URLSessionFactory {
24+
public init() {}
25+
26+
public func makeUrlSession(delegate: URLSessionDataDelegateProtocol) -> any URLSessionProtocol {
1927
let forwarder = URLSessionDataDelegateForwarder(target: delegate)
2028
return URLSession(configuration: .default, delegate: forwarder, delegateQueue: nil)
2129
}

0 commit comments

Comments
 (0)