Provider OAuth
OAuth Support
Add OAuth/SSO authentication that integrates with/login:
/login corporate-ai.
Existing extension OAuth definitions keep their login, refreshToken, getApiKey, and optional modifyModels methods. OAuth refresh is serialized so concurrent requests do not overwrite each other’s credentials. Atomic never aborts the signal passed to refreshToken on a timeout, because the provider may already have rotated the refresh token; a late result is saved, and a normal exit waits up to three seconds for it. Do not add your own timeout or abort to the token request.
OAuth callbacks run in the engine in isolated interactive mode. Use the provided callbacks for user interaction rather than relying on terminal-process state.
loginLabel sets the dialog title. usesCallbackServer: true offers a redirect-URL paste field alongside the browser callback.
For the native pi.registerProvider(provider) form, set provider.auth.oauth.usesCallbackServer: true when the flow races a callback server against interaction.prompt({ type: "manual_code", message: "Paste redirect URL:" }). This shows the paste field even under a custom provider ID. The built-in Anthropic, OpenAI, OpenAI Codex, and OpenRouter OAuth objects already carry this flag; preserve it when cloning or wrapping them. Set it explicitly to false for a replacement flow that does not use a callback server. Legacy pi.registerProvider(id, config) registrations continue to use config.oauth.usesCallbackServer.
Successful login persists credentials before refreshing the catalog. Logout removes stored credentials without calling refreshModels. Escape or Ctrl+C cancels the matching login and preserves the previous credentials and catalog. Later registrations still override earlier providers by ID.
Intentional cancellation is quiet, including native AbortError, an aborted signal or its exact reason, nested abort causes, and the legacy exact Login cancelled error. Provider denial, timeout, network/protocol errors, malformed responses, token exchange failures, and storage failures remain visible. Catalog-refresh failures are reported by /model while cached models remain selectable; they do not turn a persisted login into a failed transaction.
Rejected logins
When the provider rejects the stored refresh token, for example because the user revoked access or the login expired, throwOAuthRefreshRejectedError from refreshToken:
HTTP <status> when there is none), and the description. Pass the stored tokens as the third argument: a description that contains one of them, contains a URL, or is too long is left out. Never put tokens or request bodies in the code or description.
Atomic also treats these errors from refreshToken as a rejected login, including when they are the cause of the error you throw. The rejected-login codes are the ones the built-in providers use: invalid_grant, refresh_token_expired, refresh_token_reused, and refresh_token_invalidated.
- an error whose
errororcodeproperty is a rejected-login code, such as openid-client’s response errors; anerror_descriptionproperty supplies the description - an error whose
body,data,response.data, orresponse.bodyholds an OAuth error response with a rejected-login code inerror,error.code, orcode, as an object or JSON text, as HTTP clients such as axios attach it;error_descriptionorerror.messagesupplies the description - an error whose
status,statusCode,response.status, orresponse.statusCodeis 401, as HTTP clients such as axios and got report it - an error message that contains a rejected-login code, alone or in an OAuth error response, as the built-in providers report it
OAuth refresh failed for <provider>: <message> without asking the user to sign in again, because a retry may succeed. These include a 5xx status, a network error, and other OAuth error codes such as invalid_client, invalid_request, or server_error.
Dynamic model catalog refresh
Providers whose catalogs change at runtime can addrefreshModels. Atomic calls it during the model picker’s bounded asynchronous refresh, independently of authentication completion:
signal retains its previous list. Use the provider-scoped store only when the catalog should persist across sessions.
OAuthLoginCallbacks
Thecallbacks object provides three ways to authenticate:
/login, press Enter (tui.select.confirm) to open the device-login URL and copy the code, or log in manually. Providers can disable either action with openBrowserOnConfirm: false or copyCodeOnConfirm: false in onDeviceCode or native AuthEvent notifications.
OAuthCredentials
Credentials are persisted in~/.atomic/agent/auth.json (legacy ~/.pi/agent/auth.json may be read for compatibility):