Skip to main content

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 stateApp behavior
StoppedStop and release resources
PlayingStart playback
PausedPause this task
ResumedResume 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​

  1. The app retains final authorization. AIKit rejects exact eligibility failures; return an error through completion when local preparation fails.
  2. Handle calls, audio-session preemption, and screen teardown for app-owned PCM/TTS playback.
  3. Invoke each sink completion exactly once so AIKit can finish the playback flow.
  4. Stop all device-initiated tasks and unregister sinks before switching Contexts.
  5. inputCompletionHandler releases the input lease; it is not cancellation or a session terminal and must not stop downstream work.