Repository navigation
feat: Sign in with AssemblyAI (PE-2657) - #254
Merged
Merged
Conversation
The alternative to pasting a key: the member signs in and approves Blurt
on AssemblyAI's consent page (authorization code + PKCE, public client,
ASWebAuthenticationSession, redirect com.assemblyai.blurt://oauth/callback),
and Blurt trades the access token for the connection's own API key, which
it stores where a pasted key goes.
- AssemblyAI library: OAuthAuthorization (PKCE, consent URL, callback
check) and OAuthClient (token endpoint, POST /api/v2/oauth/api-key and
/revoke). Refused calls log the route, status and server reason, never
a token.
- Engine: AssemblyAIAccount (sign-in, renewal, sign-out), SignInSession
in its own Keychain item with a read that keeps "none" apart from
"unavailable", CredentialRows (which rows show), runRenewal (launch,
then hourly; renews daily or within two days of key expiry). A
generation counter keeps a renewal from undoing a sign-out or reset.
- Shell: AccountSignIn (the web session) and AccountRowView ("Sign In
with AssemblyAI…" / "Signed in as …" + Sign Out; recoverable failures
inline, Keychain faults as alerts). The reset clears the session too.
- Off unless a build sets the BLURT_OAUTH_* settings. They name
AssemblyAI's non-production deployments, so they live in a gitignored
App/Blurt/Config/SignIn.local.xcconfig, included by SignIn.xcconfig on
the dev configurations only (example file committed). Release has none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A key minted by a non-production sign-in only works against that deployment's dictation host, while dictation always went to production, so such a key got a 401 on every dictation. - AssemblyAITranscriber takes its host as a closure (`baseURL`), read at every request and warm-up, in place of a fixed URL. The default is `defaultBaseURL` (production); a data-zone host goes here too. - DictationOrigin (a Mutex) holds the signed-in key's host. The account keeps it current as it signs in and out, and the app's `baseURL` closure reads it, so the transcriber knows nothing of sign-in. - BLURT_OAUTH_DICTATION_ORIGIN (optional, in the gitignored local xcconfig) sets that host. Unset, or a pasted key, means production. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 tasks done
- Renewal is due when the exchange says the key rotates (api_key_rotates_at, AMS's early-rotation window), else two days before expiry. No daily rule: one renewal per key is all the refresh token's three-month sliding lifetime needs. - runRenewal sleeps until the renewal is due (at most a day per sleep, on the continuous clock so a renewal due during sleep runs on wake) instead of checking hourly. - KeyRejectionReporter wraps the transcriber: a dictation refused for its key (401, or the API's invalid-key 404) calls keyWasRejected(), which renews at once, at most every 15 minutes. The refused utterance itself can't be retried. - A renewal that brings back the same key with its renewal already due waits for the key's expiry (or a day once expired) instead of asking every minute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jscpd flagged the two renewal-race tests' identical nine-line setup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
damafeez
marked this pull request as ready for review
October 21, 2026 17:27
damafeez
requested review from
alexkroman and
he-james
and
a balanced review from Copilot
and removed request for
alexkroman and
Copilot
October 21, 2026 17:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Adds "Sign in with AssemblyAI" as an alternative to pasting a key (PE-2657, part of the Agent OAuth epic PE-2644). The member signs in and approves Blurt on AssemblyAI's consent page. Blurt then trades the access token for the connection's own API key and stores it where a pasted key goes, so dictation, the press gate and Settings read it unchanged.
Three commits:
ASWebAuthenticationSession.POST /api/v2/oauth/api-key/revoke), then clears it on the Mac. If the server can't confirm, everything stays in place and the user can retry.AssemblyAITranscribernow takes its host as abaseURLclosure, read at every request and warm-up. The app's closure returns the signed-in key's host (DictationOrigin), and production otherwise. A non-production key only works against its own deployment's dictation host.api_key_rotates_at(from DeepLearning#22337, which rotates inside a window before expiry). Without it, renewal falls back to two days before expiry.runRenewalsleeps until then instead of checking hourly.KeyRejectionReporterrenews at once when dictation refuses the key.Off by default. A build turns it on with
BLURT_OAUTH_*settings. They name AssemblyAI's non-production deployments, so they aren't in this repo:App/Blurt/Config/SignIn.local.xcconfig, whichSignIn.xcconfigincludes for the dev configurations only.SignIn.local.xcconfig.exampleto set it up.AGENTS.md(Settings, persistence) describes the design.How it was tested
scripts/check.shpasses locally on each commit: engine coverage 89.1%, iOS check green, swift-format and swiftlint cleanAGENTS.md,Sources/AssemblyAI/README.md,Sources/BlurtEngine/README.mdUnit tests:
End to end against a non-production deployment (ad-hoc Debug build):
Reviewed by the repo's three agents: swift6-concurrency, macos-hig and cleanup. Their findings are applied.
🤖 Generated with Claude Code
Screenshots
