Files
Moltbot/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayErrors.swift
2026-03-12 22:23:07 +00:00

147 lines
5.9 KiB
Swift

import OpenClawProtocol
import Foundation
public enum GatewayConnectAuthDetailCode: String, Sendable {
case authRequired = "AUTH_REQUIRED"
case authUnauthorized = "AUTH_UNAUTHORIZED"
case authTokenMismatch = "AUTH_TOKEN_MISMATCH"
case authBootstrapTokenInvalid = "AUTH_BOOTSTRAP_TOKEN_INVALID"
case authDeviceTokenMismatch = "AUTH_DEVICE_TOKEN_MISMATCH"
case authTokenMissing = "AUTH_TOKEN_MISSING"
case authTokenNotConfigured = "AUTH_TOKEN_NOT_CONFIGURED"
case authPasswordMissing = "AUTH_PASSWORD_MISSING"
case authPasswordMismatch = "AUTH_PASSWORD_MISMATCH"
case authPasswordNotConfigured = "AUTH_PASSWORD_NOT_CONFIGURED"
case authRateLimited = "AUTH_RATE_LIMITED"
case authTailscaleIdentityMissing = "AUTH_TAILSCALE_IDENTITY_MISSING"
case authTailscaleProxyMissing = "AUTH_TAILSCALE_PROXY_MISSING"
case authTailscaleWhoisFailed = "AUTH_TAILSCALE_WHOIS_FAILED"
case authTailscaleIdentityMismatch = "AUTH_TAILSCALE_IDENTITY_MISMATCH"
case pairingRequired = "PAIRING_REQUIRED"
case controlUiDeviceIdentityRequired = "CONTROL_UI_DEVICE_IDENTITY_REQUIRED"
case deviceIdentityRequired = "DEVICE_IDENTITY_REQUIRED"
case deviceAuthInvalid = "DEVICE_AUTH_INVALID"
case deviceAuthDeviceIdMismatch = "DEVICE_AUTH_DEVICE_ID_MISMATCH"
case deviceAuthSignatureExpired = "DEVICE_AUTH_SIGNATURE_EXPIRED"
case deviceAuthNonceRequired = "DEVICE_AUTH_NONCE_REQUIRED"
case deviceAuthNonceMismatch = "DEVICE_AUTH_NONCE_MISMATCH"
case deviceAuthSignatureInvalid = "DEVICE_AUTH_SIGNATURE_INVALID"
case deviceAuthPublicKeyInvalid = "DEVICE_AUTH_PUBLIC_KEY_INVALID"
}
public enum GatewayConnectRecoveryNextStep: String, Sendable {
case retryWithDeviceToken = "retry_with_device_token"
case updateAuthConfiguration = "update_auth_configuration"
case updateAuthCredentials = "update_auth_credentials"
case waitThenRetry = "wait_then_retry"
case reviewAuthConfiguration = "review_auth_configuration"
}
/// Structured websocket connect-auth rejection surfaced before the channel is usable.
public struct GatewayConnectAuthError: LocalizedError, Sendable {
public let message: String
public let detailCodeRaw: String?
public let recommendedNextStepRaw: String?
public let canRetryWithDeviceToken: Bool
public init(
message: String,
detailCodeRaw: String?,
canRetryWithDeviceToken: Bool,
recommendedNextStepRaw: String? = nil)
{
let trimmedMessage = message.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedDetailCode = detailCodeRaw?.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedRecommendedNextStep =
recommendedNextStepRaw?.trimmingCharacters(in: .whitespacesAndNewlines)
self.message = trimmedMessage.isEmpty ? "gateway connect failed" : trimmedMessage
self.detailCodeRaw = trimmedDetailCode?.isEmpty == false ? trimmedDetailCode : nil
self.canRetryWithDeviceToken = canRetryWithDeviceToken
self.recommendedNextStepRaw =
trimmedRecommendedNextStep?.isEmpty == false ? trimmedRecommendedNextStep : nil
}
public init(
message: String,
detailCode: String?,
canRetryWithDeviceToken: Bool,
recommendedNextStep: String? = nil)
{
self.init(
message: message,
detailCodeRaw: detailCode,
canRetryWithDeviceToken: canRetryWithDeviceToken,
recommendedNextStepRaw: recommendedNextStep)
}
public var detailCode: String? { self.detailCodeRaw }
public var recommendedNextStepCode: String? { self.recommendedNextStepRaw }
public var detail: GatewayConnectAuthDetailCode? {
guard let detailCodeRaw else { return nil }
return GatewayConnectAuthDetailCode(rawValue: detailCodeRaw)
}
public var recommendedNextStep: GatewayConnectRecoveryNextStep? {
guard let recommendedNextStepRaw else { return nil }
return GatewayConnectRecoveryNextStep(rawValue: recommendedNextStepRaw)
}
public var errorDescription: String? { self.message }
public var isNonRecoverable: Bool {
switch self.detail {
case .authTokenMissing,
.authBootstrapTokenInvalid,
.authTokenNotConfigured,
.authPasswordMissing,
.authPasswordMismatch,
.authPasswordNotConfigured,
.authRateLimited,
.pairingRequired,
.controlUiDeviceIdentityRequired,
.deviceIdentityRequired:
return true
default:
return false
}
}
}
/// Structured error surfaced when the gateway responds with `{ ok: false }`.
public struct GatewayResponseError: LocalizedError, @unchecked Sendable {
public let method: String
public let code: String
public let message: String
public let details: [String: AnyCodable]
public init(method: String, code: String?, message: String?, details: [String: AnyCodable]?) {
self.method = method
self.code = (code?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false)
? code!.trimmingCharacters(in: .whitespacesAndNewlines)
: "GATEWAY_ERROR"
self.message = (message?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false)
? message!.trimmingCharacters(in: .whitespacesAndNewlines)
: "gateway error"
self.details = details ?? [:]
}
public var errorDescription: String? {
if self.code == "GATEWAY_ERROR" { return "\(self.method): \(self.message)" }
return "\(self.method): [\(self.code)] \(self.message)"
}
}
public struct GatewayDecodingError: LocalizedError, Sendable {
public let method: String
public let message: String
public init(method: String, message: String) {
self.method = method
self.message = message
}
public var errorDescription: String? { "\(self.method): \(self.message)" }
}