Skip to content
Open
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
36 changes: 36 additions & 0 deletions Sources/Fluid/Services/ASRService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3169,6 +3169,14 @@ final class ASRService: ObservableObject {
if Task.isCancelled || Self.isModelPreparationCancellation(error) {
throw CancellationError()
}
if Self.isRateLimitOrNetworkError(error) {
DebugLogger.shared.warning(
"ASRService: Prepare failed for \(provider.name) due to rate limiting or a network error; " +
"skipping cache-clear recovery so cached files stay resumable: \(error)",
source: "ASRService"
)
throw error
}
firstError = error
DebugLogger.shared.error("ASRService: First prepare attempt for \(provider.name) failed after \(String(format: "%.2f", Date().timeIntervalSince(start)))s", source: "ASRService")
DebugLogger.shared.warning(
Expand Down Expand Up @@ -3228,6 +3236,34 @@ final class ASRService: ObservableObject {
return nsError.domain == NSURLErrorDomain && nsError.code == NSURLErrorCancelled
}

/// Rate-limit and transport failures must not trigger the cache-clear recovery: clearing the
/// cache forces a full re-download, which burns more of HuggingFace's fixed per-IP API window
/// and turns a transient 429 into a self-reinforcing failure loop (issue #683).
private nonisolated static func isRateLimitOrNetworkError(_ error: Error) -> Bool {
// Providers may rebox the download error, so walk the underlying-error chain.
var current: Error? = error
var depth = 0
while let candidate = current, depth < 5 {
#if arch(arm64)
if let hfError = candidate as? DownloadUtils.HuggingFaceDownloadError {
switch hfError {
case .rateLimited, .htmlErrorResponse:
Comment on lines +3248 to +3250

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard FluidAudio-only error handling on Intel builds

In the Intel build path, ASRService.swift does not import FluidAudio because the import at the top is behind #if arch(arm64), and the repo provides Intel stubs for FluidAudio-backed providers so Whisper/Apple Speech can still build. This new unconditional reference to DownloadUtils.HuggingFaceDownloadError therefore leaves DownloadUtils out of scope for x86_64 macOS builds, breaking the Intel support path; wrap this branch in the same architecture/import guard or avoid the FluidAudio type outside arm64 code.

Useful? React with πŸ‘Β / πŸ‘Ž.

return true
case .invalidResponse, .downloadFailed, .modelNotFound:
return false
}
}
#endif
let nsError = candidate as NSError
if nsError.domain == NSURLErrorDomain {
return nsError.code != NSURLErrorCancelled
}
current = nsError.userInfo[NSUnderlyingErrorKey] as? Error
depth += 1
}
return false
}

// MARK: - Model lifecycle helpers (parity with original API)

func predownloadSelectedModel() {
Expand Down
Loading