Device-Initiated AI
TopStepAIKit handles device-origin chat, recording, voice translation, question answering, watch-face creation, and ride hailing. Apps can also start the same device-coordinated sessions. The SDK owns DeviceBridge/provider orchestration; the app owns local preparation, screen lifecycle, and business output.
Start eligibility
Use supportsAIFeatures: only to decide whether to show an entry point. Authorize a device-coordinated start by evaluating a complete TSAIStartRequest with startEligibilityForRequest:. See Capability and Start Eligibility.
Generic device sessions
TSAIContext conforms to TSAIDeviceAISessionInterface. Use this generic interface for voice translation, voice question answering, watch-face creation, and ride hailing. Chat and recording retain their dedicated APIs.
Register a device-origin route
TSAIContext *context = [TSAIKit sharedInstance].activeContext;
[context registerDeviceAISessionHandlerForUseCase:TSAIUseCaseVoiceTranslation
prepareHandler:^(TSAIStartRequest *request, TSAICompletionBlock completion) {
// Prepare non-UI resources such as AudioSession and business state.
completion(YES, nil);
}
activationHandler:^(TSAIStartRequest *request) {
// Both sides are active. Open the business UI only here.
}
inputCompletionHandler:^(TSAIStartRequest *request) {
// Device capture ended naturally; ASR, translation, and TTS may continue.
}
terminationHandler:^(TSAIStartRequest *request, BOOL interrupted, NSError *error) {
// Idempotently release resources and dismiss UI.
}
voiceDataHandler:nil];
- (void)registerDeviceAISessionHandlerForUseCase:(TSAIUseCase)useCase
prepareHandler:(nullable TSAIDeviceAISessionPrepareHandler)prepareHandler
activationHandler:(nullable TSAIDeviceAISessionActivationHandler)activationHandler
inputCompletionHandler:(nullable TSAIDeviceAISessionInputCompletionHandler)inputCompletionHandler
terminationHandler:(nullable TSAIDeviceAISessionTerminationHandler)terminationHandler
voiceDataHandler:(nullable TSAIDeviceAISessionVoiceDataHandler)voiceDataHandler;
inputCompletionHandler is required when registering. voiceDataHandler is also required for ride hailing with Opus input and receives ordered opusData, optional pcmData, and isFinal values.
Start and stop from the app
TSAIStartEligibility *eligibility = [context startEligibilityForRequest:request];
if (eligibility.support == TSAICapabilitySupported) {
[context startDeviceAISessionFromAppWithRequest:request
completion:^(BOOL success, NSError *error) {
if (!success) {
TSLog(@"Device coordination failed: %@", error.localizedDescription);
}
}];
}
[context stopDeviceAISessionWithRequest:request completion:nil];
- (void)startDeviceAISessionFromAppWithRequest:(TSAIStartRequest *)request
completion:(nullable TSAICompletionBlock)completion;
- (void)stopDeviceAISessionWithRequest:(TSAIStartRequest *)request
completion:(nullable TSAICompletionBlock)completion;
Start succeeds only after local preparation and device synchronization complete. Stop with the exact request received by the handlers or used to start.
Conversation-translation product mode
- (void)startDeviceConversationTranslationWithMode:
(TSAIConversationTranslationMode)mode
completion:(nullable TSAICompletionBlock)completion;
- (void)stopDeviceConversationTranslationWithCompletion:
(nullable TSAICompletionBlock)completion;
FaceToFace, Private, and Portable control the device page and pickup layout. Each translation round still uses device-session orchestration.
Device-initiated voice chat
[context.assistant registerOnAIChatDeviceEvent:^(TSAIChatDeviceEvent event) {
switch (event) {
case TSAIChatDeviceEventRequestStart:
// Prepare with startChatWithConfig:. Do not present UI yet.
break;
case TSAIChatDeviceEventRequestEnd:
// Call stopChatWithTaskId: and dismiss UI.
break;
case TSAIChatDeviceEventInterrupted:
// End UI and surface the interruption.
break;
}
}];
The event is a request signal and does not automatically change SDK state. After RequestStart, prepare locally and present UI only when onEvent receives TSAIChatEventTypeSessionStarted.
Device-microphone requests
Reject a request explicitly when the app cannot continue:
[context.speech rejectDeviceMicRecognitionWithCompletion:^(BOOL success, NSError *error) {
if (!success) {
TSLog(@"Failed to reject device microphone request: %@", error.localizedDescription);
}
}];
When accepted, start recognizeSpeechWithDeviceMicConfig: and end normally with stopDeviceMicRecognitionWithTaskId:.
Device-initiated voice translation
AIKit owns device audio, ASR, translation, and TTS. The app implements TSAIDeviceVoiceTranslationOutputSink to buffer and play signed Int16 little-endian PCM.
[context registerDeviceVoiceTranslationOutputSink:self];
The Context holds the sink weakly. Unregister it before the owner is destroyed.
- (void)prepareForTaskId:(NSString *)taskId
sampleRate:(NSInteger)sampleRate
channelCount:(NSInteger)channelCount;
- (void)appendInt16PCMData:(NSData *)pcmData taskId:(NSString *)taskId;
- (void)finishAndPlayTaskId:(NSString *)taskId;
- (void)setPlaybackState:(TSAIDeviceVoicePlaybackState)state
forTaskId:(NSString *)taskId;
- (void)discardTaskId:(NSString *)taskId;
| Playback state | App behavior |
|---|---|
Stopped | Stop and release resources |
Playing | Start playback |
Paused | Pause this task |
Resumed | Resume this task |
Keep separate buffers for different task IDs and append PCM in callback order.
Device-initiated question answering
AIKit owns ASR, answer generation, and TTS. The app implements the complete-audio sink:
@interface MyPlayer () <TSAIDeviceQuestionAnswerOutputSink>
@end
[context registerQuestionAnswerOutputSink:self];
- (void)playQuestionAnswerTTSResult:(TSAITTSResult *)result
taskId:(NSString *)taskId
completion:(nullable TSAICompletionBlock)completion;
- (void)stopQuestionAnswerPlaybackWithTaskId:(NSString *)taskId;
A streaming player may additionally conform to TSAIDeviceQuestionAnswerStreamingOutputSink:
- (void)appendQuestionAnswerTTSStreamChunk:(TSAITTSStreamChunk *)chunk
taskId:(NSString *)taskId;
- (void)finishQuestionAnswerTTSStreamWithTaskId:(NSString *)taskId
completion:(nullable TSAICompletionBlock)completion;
AIKit invokes streaming chunks on the main thread in sequence order. It falls back to complete TSAITTSResult playback when the sink does not support the streaming extension.
Device-initiated AI recording
[context.audioRecord registerOnRequestStartAIAudioRecording:^(TSAIAudioRecordScene scene) {
// Check consent, storage, network, and task conflicts before starting.
}];
[context.audioRecord registerOnRequestStopAIAudioRecording:^{
// Stop and report the device-side state.
}];
See AI Recording.
Cleanup
[context registerDeviceVoiceTranslationOutputSink:nil];
[context registerQuestionAnswerOutputSink:nil];
[context.assistant registerOnAIChatDeviceEvent:nil];
[context.speech registerDeviceMicRecognitionStateDidChanged:nil];
[context.audioRecord registerOnRequestStartAIAudioRecording:nil];
[context.audioRecord registerOnRequestStopAIAudioRecording:nil];
Important notes
- The app retains final authorization. AIKit rejects exact eligibility failures; return an error through completion when local preparation fails.
- Handle calls, audio-session preemption, and screen teardown for app-owned PCM/TTS playback.
- Invoke each sink completion exactly once so AIKit can finish the playback flow.
- Stop all device-initiated tasks and unregister sinks before switching Contexts.
inputCompletionHandlerreleases the input lease; it is not cancellation or a session terminal and must not stop downstream work.