swiftsiri-intent

Siri-Intent indicates no Shortcut, but message is not clear


I am seeing problems with my intent handler showing up in my logs that I don't understand.
Here's the relevant code

 func addAttendee(numberToAdd: Int) -> Int {
        self.numberOfAttendees += numberToAdd
        let intent = AddAttendeeIntent()
        intent.suggestedInvocationPhrase = "Add People"
        intent.people = NSNumber(value: numberToAdd)

        let interaction = INInteraction(intent: intent, response: nil)

        interaction.donate { (error) in
            if error != nil {
                if let error = error as NSError? {
                    self.logger.log("error occurred: \(error, privacy: .public)")
                }
            } else {
                self.logger.log("Add people Intent success")
            }
        }
        return (self.numberOfAttendees)
    }

The error I am seeing is as follows:

error occurred: Error Domain=IntentsErrorDomain Code=1901 "Cannot donate interaction with intent that has no valid shortcut types: <INInteraction: 0x6000002b0480> {
    intent = <INIntent: 0x6000014b0510> {
    };
    dateInterval = <_NSConcreteDateInterval: 0x600002670da0> (Start Date) 2023-03-05 01:30:08 +0000 + (Duration) 0.000000 seconds = (End Date) 2023-03-05 01:30:08 +0000;
    intentResponse = <null>;
    groupIdentifier = <null>;
    intentHandlingStatus = Unspecified;
    identifier = 8E92F53E-C532-4C3A-A58A-E30E2177A227;
    direction = Unspecified;
} for intent <AddAttendeeIntent: 0x600001490090> {
    people = 1;
}" UserInfo={NSLocalizedDescription=Cannot donate interaction with intent that has no valid shortcut types: <INInteraction: 0x6000002b0480> {
    intent = <INIntent: 0x6000014b0510> {
    };
    dateInterval = <_NSConcreteDateInterval: 0x600002670da0> (Start Date) 2023-03-05 01:30:08 +0000 + (Duration) 0.000000 seconds = (End Date) 2023-03-05 01:30:08 +0000;
    intentResponse = <null>;
    groupIdentifier = <null>;
    intentHandlingStatus = Unspecified;
    identifier = 8E92F53E-C532-4C3A-A58A-E30E2177A227;
    direction = Unspecified;
} for intent <AddAttendeeIntent: 0x600001490090> {
    people = 1;
}}

Looking at my Intent definitions I should be ok: Intent Definition Screen shot

And my intent handler is pretty simple too

    override func handler(for intent: INIntent) -> Any {
        // This is the default implementation.  If you want different objects to handle different intents,
        // you can override this and return the handler you want for that particular intent.
        logger.log("\(intent)")

        switch intent {
        case is AddAttendeeIntent:
            return AddAttendeeIntentHandler()
        case is RemoveAttendeeIntent:
            return RemoveAttendeeIntentHandler()
        case is StartMeetingIntent:
            return StartMeetingIntentHandler()
        case is EndMeetingIntent:
            return EndMeetingIntent()
        case is ResetMeetingIntent:
            return ResetMeetingIntent()
        case is QuorumReachedIntent:
            return QuorumReachedIntent()
        default:
            fatalError("Shortcut - No handler for this intent")
        }
    }

I am not sure what is meant by no valid shortcut type. If I change my Intent Handler to have a case of AddAttendee, it indicates that it does not exist. Any specific thing I am not seeing?


Solution

  • I converted my code to App Shortcut following the Migrating to App Short cuts tech talk from apple and it now works. Here's the resultant code:

    //
    //  AddAttendee.swift
    //  
    //
    //  Created by Michael Rowe on 3/4/23.
    //
    
    import Foundation
    import AppIntents
    
    @available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *)
    struct AddAttendee: AppIntent, CustomIntentMigratedAppIntent, PredictableIntent {
        static let intentClassName = "AddAttendeeIntent"
    
        static var title: LocalizedStringResource = "Add people to a meeting"
        static var description = IntentDescription("Add people to a meeting")
    
        @Parameter(title: "People", default: 1, requestValueDialog: "How many people do you want to add?")
        var people: Int
    
        static var parameterSummary: some ParameterSummary {
            Summary("Add \(\.$people) people to a meeting.")
        }
    
        static var predictionConfiguration: some IntentPredictionConfiguration {
            IntentPrediction(parameters: (\.$people)) { people in
                DisplayRepresentation(
                    title: "Add \(people) people to a meeting.",
                    subtitle: ""
                )
            }
        }
    
        func perform() async throws -> some IntentResult & ReturnsValue<Int> {
            // First attempt at converting to AppIntent
            if people < 0 {
                throw AddAttendeeError.negativeNumbersNotAllowed(people)
            } else if people > 1000 {
                throw AddAttendeeError.greaterThanMaximumValue(people)
            }
    
            try await requestConfirmation(result: .result(
                value: people,
                dialog: "Adding \(people) attendees, is that right?"
            ),
                confirmationActionName: .add,
                showPrompt: false)
    
            return .result(value: people, dialog: "Added \(people) attendees to meeting")
        }
    }
    
    enum AddAttendeeError: Error, CustomLocalizedStringResourceConvertible {
        case negativeNumbersNotAllowed(Int)
        case greaterThanMaximumValue(Int)
    
        var localizedStringResource: LocalizedStringResource {
            switch self {
            case .negativeNumbersNotAllowed(let people):
                return "You can't add \(people) attendees"
            case .greaterThanMaximumValue(let people):
                return "You can't add \(people) attendees"
            }
        }
    }
    
    fileprivate extension IntentDialog {
        static var peopleParameterPrompt: Self {
            "How many people do you want to add?"
        }
        static func responseSuccess(result: Int) -> Self {
            "\(result)"
        }
        static func responseFailure(error: String) -> Self {
            "\(error)"
        }
    }