Skip to main content

Provider OAuth

OAuth Support

Add OAuth/SSO authentication that integrates with /login:
After registration, users can authenticate via /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, throw OAuthRefreshRejectedError from refreshToken:
Atomic then handles the refresh as it does for a built-in provider. It first uses a newer login that another session saved, if there is one. Otherwise the request fails with:
The message shows only the provider ID, the error code (or 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 error or code property is a rejected-login code, such as openid-client’s response errors; an error_description property supplies the description
  • an error whose body, data, response.data, or response.body holds an OAuth error response with a rejected-login code in error, error.code, or code, as an object or JSON text, as HTTP clients such as axios attach it; error_description or error.message supplies the description
  • an error whose status, statusCode, response.status, or response.statusCode is 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
Other failures are reported as 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 add refreshModels. Atomic calls it during the model picker’s bounded asynchronous refresh, independently of authentication completion:
The current catalog stays readable while refresh is pending. Successful provider results are applied independently; a provider that fails, times out, or observes an aborted signal retains its previous list. Use the provider-scoped store only when the catalog should persist across sessions.

OAuthLoginCallbacks

The callbacks object provides three ways to authenticate:
In /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):