SDKiOS

Initialization (iOS)

Initialize the Extentos iOS SDK with Extentos.create(config:), forward the Meta DAT auth callback via .onOpenURL and handleUrl, request speech authorization, and reach the typed sub-clients.

The symbols below are verified against the current iOS source. The agent-driven pathgenerateConnectionModule(platform: "ios") — emits this wiring for you; package install: Install.

Create the instance

The entry point is the Extentos enum. Call Extentos.create(config:) with an ExtentosConfig, or Extentos.default() for defaults. Both return an ExtentosGlasses — the root object that owns every capability sub-client.

import GlassesCore

let glasses = Extentos.create(config: ExtentosConfig(
    appId: "com.example.myapp",
    transport: .auto,
    usedCapabilities: [.microphone, .speaker],
))

usedCapabilities is not decoration on iOS — it picks the transport (see the callout below). Declare your real footprint: [.microphone, .speaker] for a voice app, plus .camera / .display if you use them. Leave debug at its default unless you are driving the browser-simulator dev loop; debug: true opens a pending simulator socket and waits for the MCP bridge to bind it.

transport: .auto walks these rules in order, stopping at the first match:

#ConditionResult
1config.debug and a session URL baked in by the scaffold.browserSim
2config.debug and EXTENTOS_SESSION_URL in the environment.browserSim
3hasBondedMetaDevice returns true.realMeta
4config.debug (no URL, no bonded device).browserSim in pending mode — waits for the MCP localhost bridge to bind it
5ausedCapabilities is non-empty and contains neither .camera nor .display.systemAudio — the vendorless audio baseline
5banything else (including an empty usedCapabilities).realMeta; connect() surfaces NoEligibleDevice when none are paired

Every debug above is the ExtentosConfig.debug field, not your Xcode build configuration. It defaults to false, so a normal Debug-configuration run of a voice app skips rungs 1, 2 and 4 and lands on 5a — the audio baseline — exactly like a release build. Set debug: true only when you are driving the browser-simulator dev loop; on a phone with no bridge running it parks on rung 4 and waits.

It never falls back to a simulator on its own.

On iOS, usedCapabilities decides your transport — it is not just connection-page decoration. iOS has no API for enumerating bonded Bluetooth devices, so unlike Android there is no way to detect "are the glasses actually here?"; the declared footprint is the only honest signal. An empty usedCapabilities resolves to real glasses, which is right for a camera app but wrong for a voice app — on a phone with no glasses paired, connect() returns NoEligibleDevice instead of falling back to the phone's own mic and speaker. A voice-only app must declare its footprint explicitly:

ExtentosConfig(usedCapabilities: [.microphone, .speaker])

The empty default is deliberate — silently downgrading an app that declared nothing would strip camera support from existing integrations. See runtime internals.

The simulator runs the same SDK code as hardware; see transport vs app simulation. Hold a single instance for the app's lifetime.

Extentos.create(config:) is the customer entry point. Wearables.configure(...) is an internal Meta DAT type — you never call it directly.

Forward the auth callback (.onOpenURL)

Pairing with Meta Ray-Ban hands control to Meta's app and returns through your app's custom URL scheme. Catch that callback in SwiftUI's .onOpenURL and forward it to glasses.handleUrl(_:), which returns true when the SDK consumed the URL:

import SwiftUI

@main
struct MyApp: App {
    let glasses = Extentos.default()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    Task { _ = await glasses.handleUrl(url) }
                }
        }
    }
}

handleUrl(_:) is async and returns a Bool. The simulator transports return false unconditionally — only the real-Meta transport consumes the registration callback. The matching Info.plist CFBundleURLTypes entry must register the scheme, or iOS never delivers the URL.

Request speech authorization (optional)

On-device transcription (glasses.audio.transcriptions()) uses SFSpeechRecognizer, which needs NSSpeechRecognitionUsageDescription in the Info.plist and a one-time user grant. The SDK requests it on first use, but you can surface the prompt at a controlled moment — e.g. during onboarding:

let granted = await Extentos.requestSpeechRecognitionAuthorization()

It returns true when authorized.

The microphone is a separate grant, and it's yours. Speech-recognition authorization does not cover audio capture. Request both before starting the assistant — iOS will otherwise prompt mid-session, or capture silence:

// AVAudioApplication is iOS 17+. On an iOS 16 target use
// AVAudioSession.sharedInstance().requestRecordPermission { granted in … }
let mic = await AVAudioApplication.requestRecordPermission()
let speech = await Extentos.requestSpeechRecognitionAuthorization()
guard mic, speech else { return }   // surface your own explanation
try await glasses.assistant.start(provider: .managed()) {  }

Both need their Info.plist strings (NSMicrophoneUsageDescription, NSSpeechRecognitionUsageDescription) or the app is terminated on first use — see Info.plist. This is the iOS counterpart of the Android sequencing in the voice-assistant guide; iOS needs no foreground service, only the audio background mode.

The sub-client surface

ExtentosGlasses exposes typed capability sub-clients. Reach a primitive through its client — e.g. glasses.camera.capturePhoto(), glasses.audio.transcriptions(), glasses.audio.speak(...).

Sub-clientAlways presentWhat it covers
connectionyesOpen / close the glasses connection; connection-state stream.
camerayesPhoto capture, video capture, frame streams.
audioyesTranscription, audio capture, speak, audio chunks.
runtimeyesThe runtime event stream — toggleChanged, coexistenceWarning, log, unrecognizedUtterance, and assistant lifecycle events.
togglesyesThe runtime capability gates.
voiceyesConvenience surface over audio.transcriptions() that also announces your wake phrase to the simulator and connection page.
telemetryyesTelemetry events.
observabilityyesWraps BYOK AI calls so they appear under the event log's ai chip.
assistantyesThe voice-AI assistant runtime — see below.
displayyesDeclarative display trees for display-capable glasses (show / clear); display.isAvailable gates, and calls on no-display devices are silent no-ops.
capabilitiesyesDeviceCapabilitySet — flat yes/no hardware facts of the connected device; display flips live with the device. A value property, not a method client.
conversationremovedPhase-3 conversation runtime — removed (Android in 1.4.0; never shipped in the published iOS package). Historical only.
airemovedPhase-3 BYOK LLM completion surface — removed alongside conversation. Historical only.

The root object also exposes usedCapabilities (the declared capability footprint from ExtentosConfig) and a connect() convenience — what the generated bootstrap calls, equivalent to connection.connect().

Assistant

The assistant client is the voice-AI runtime. It routes through the Extentos managed gateway on both platforms — no API key in code. Start a session with the sugar form (builds the config inline), or use the raw form createSession(config:) + session.start():

let session = try await glasses.assistant.start(provider: .managed()) {
    $0.instructions = "You are a helpful assistant on smart glasses."
}

An AssistantSession has a wake/sleep cycle: start() lands it Dormant, wake() opens the realtime connection, sleep() closes it but keeps the session (history, tools, hooks) ready for the next wake, stop() tears it down for good. See the iOS API reference for the full session surface and the agent code examples for canonical flows.

Next