@@ -10,7 +10,7 @@ final class CloudRewriteService {
1010
1111 /// Wall-clock timeout for the entire rewrite operation.
1212 /// If the API doesn't respond within this duration, the original text is used.
13- private static let rewriteTimeout : Duration = . seconds( 2 )
13+ private static let rewriteTimeout : Duration = . seconds( 5 )
1414
1515 /// Models to try in priority order. First available model wins.
1616 private static let preferredModels = [
@@ -114,11 +114,17 @@ final class CloudRewriteService {
114114
115115 guard let apiKey = apiKeyProvider ( ) , !apiKey. isEmpty else {
116116 cloudRewriteLogger. debug ( " Cloud rewrite skipped: no API key " )
117+ AnalyticsService . track ( " CloudRewrite.Completed " , parameters: [
118+ " outcome " : " noApiKey " ,
119+ " inputLengthBucket " : AnalyticsService . lengthBucket ( count: text. count) ,
120+ ] )
117121 return text
118122 }
119123
124+ let startTime = ContinuousClock . now
125+
120126 do {
121- return try await withThrowingTaskGroup ( of: String . self) { group in
127+ let result = try await withThrowingTaskGroup ( of: String . self) { group in
122128 // Race 1: the actual API call
123129 group. addTask { [ self ] in
124130 try await self . rewrite ( text: text, apiKey: apiKey)
@@ -137,20 +143,58 @@ final class CloudRewriteService {
137143 group. cancelAll ( )
138144 return result
139145 }
146+
147+ let elapsed = startTime. duration ( to: . now)
148+ let elapsedMs = Int ( elapsed. components. seconds * 1000 ) + Int( elapsed. components. attoseconds / 1_000_000_000_000_000 )
149+ let resolvedModel = await modelProbeTask. value
150+ AnalyticsService . track ( " CloudRewrite.Completed " , parameters: [
151+ " outcome " : " rewritten " ,
152+ " latencyBucket " : AnalyticsService . latencyBucket ( ms: elapsedMs) ,
153+ " model " : resolvedModel,
154+ " changed " : " \( result != text) " ,
155+ " inputLengthBucket " : AnalyticsService . lengthBucket ( count: text. count) ,
156+ ] )
157+ return result
140158 } catch is RewriteTimeoutError {
141159 cloudRewriteLogger. warning ( " Cloud rewrite timed out ( \( Self . rewriteTimeout, privacy: . public) ), using original text ( \( text. count, privacy: . public) chars) " )
160+ AnalyticsService . track ( " CloudRewrite.Completed " , parameters: [
161+ " outcome " : " timeout " ,
162+ " latencyBucket " : " timeout " ,
163+ " inputLengthBucket " : AnalyticsService . lengthBucket ( count: text. count) ,
164+ ] )
142165 return text
143166 } catch is CancellationError {
144167 cloudRewriteLogger. debug ( " Cloud rewrite cancelled " )
145168 return text
146169 } catch let error as URLError {
147170 cloudRewriteLogger. warning ( " Cloud rewrite network error: \( error. code. rawValue, privacy: . public) . Fallback to original text " )
171+ let elapsedMs = Int ( startTime. duration ( to: . now) . components. seconds * 1000 )
172+ AnalyticsService . track ( " CloudRewrite.Completed " , parameters: [
173+ " outcome " : " error " ,
174+ " latencyBucket " : AnalyticsService . latencyBucket ( ms: elapsedMs) ,
175+ " errorType " : " network_ \( error. code. rawValue) " ,
176+ " inputLengthBucket " : AnalyticsService . lengthBucket ( count: text. count) ,
177+ ] )
148178 return text
149179 } catch let error as CloudRewriteError {
150180 cloudRewriteLogger. warning ( " Cloud rewrite service error: \( error. logDescription, privacy: . public) . Fallback to original text " )
181+ let elapsedMs = Int ( startTime. duration ( to: . now) . components. seconds * 1000 )
182+ AnalyticsService . track ( " CloudRewrite.Completed " , parameters: [
183+ " outcome " : " error " ,
184+ " latencyBucket " : AnalyticsService . latencyBucket ( ms: elapsedMs) ,
185+ " errorType " : error. logDescription,
186+ " inputLengthBucket " : AnalyticsService . lengthBucket ( count: text. count) ,
187+ ] )
151188 return text
152189 } catch {
153190 cloudRewriteLogger. warning ( " Cloud rewrite unknown error: \( error. localizedDescription, privacy: . public) . Fallback to original text " )
191+ let elapsedMs = Int ( startTime. duration ( to: . now) . components. seconds * 1000 )
192+ AnalyticsService . track ( " CloudRewrite.Completed " , parameters: [
193+ " outcome " : " error " ,
194+ " latencyBucket " : AnalyticsService . latencyBucket ( ms: elapsedMs) ,
195+ " errorType " : " unknown " ,
196+ " inputLengthBucket " : AnalyticsService . lengthBucket ( count: text. count) ,
197+ ] )
154198 return text
155199 }
156200 }
@@ -160,7 +204,7 @@ final class CloudRewriteService {
160204
161205 var request = URLRequest ( url: endpoint)
162206 request. httpMethod = " POST "
163- request. timeoutInterval = 3 // Safety net; task group timeout (2s ) is the primary enforcer
207+ request. timeoutInterval = 8 // Safety net; task group timeout (5s ) is the primary enforcer
164208 request. setValue ( " application/json " , forHTTPHeaderField: " Content-Type " )
165209 request. setValue ( " Bearer \( apiKey) " , forHTTPHeaderField: " Authorization " )
166210
0 commit comments