In RPC mode, loaf handles OAuth authentication flows via events rather than automatically opening browser windows. This allows headless integration and custom UI implementations.
OAuth Behavior in RPC Mode
When rpcMode: true is set:
- No automatic browser opening: The server emits URLs as events instead
- Event-driven flow: Clients receive
auth.flow.* events to coordinate authentication
- Device code support: OpenAI supports both browser and device code flows
OpenAI OAuth
Initiating the Flow
{
"jsonrpc": "2.0",
"id": 1,
"method": "auth.connect.openai",
"params": {
"mode": "browser",
"originator": "my-client"
}
}
Parameters:
mode (optional): "auto" | "browser" | "device_code"
"auto": Server chooses best method
"browser": Browser-based OAuth
"device_code": Device code flow
originator (optional): Client identifier for tracking
Event Sequence (Browser Mode)
1. Flow Started
{
"jsonrpc": "2.0",
"method": "event",
"params": {
"type": "auth.flow.started",
"timestamp": "2026-03-04T12:00:00.000Z",
"payload": {
"provider": "openai"
}
}
}
2. Browser URL Available
{
"jsonrpc": "2.0",
"method": "event",
"params": {
"type": "auth.flow.url",
"timestamp": "2026-03-04T12:00:01.000Z",
"payload": {
"provider": "openai",
"url": "https://auth0.openai.com/authorize?..."
}
}
}
Clients must open the URL in a browser or present it to the user. The server waits for the OAuth callback.
3. Flow Completed
{
"jsonrpc": "2.0",
"method": "event",
"params": {
"type": "auth.flow.completed",
"timestamp": "2026-03-04T12:00:30.000Z",
"payload": {
"provider": "openai",
"login_method": "browser",
"account_id": "org-abc123"
}
}
}
Final Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"provider": "openai",
"login_method": "browser",
"account_id": "org-abc123"
}
}
Event Sequence (Device Code Mode)
1. Flow Started (same as above)
2. Device Code Available
{
"jsonrpc": "2.0",
"method": "event",
"params": {
"type": "auth.flow.device_code",
"timestamp": "2026-03-04T12:00:01.000Z",
"payload": {
"provider": "openai",
"verification_url": "https://auth0.openai.com/activate",
"user_code": "ABCD-1234",
"interval_seconds": 5,
"expires_in_seconds": 600
}
}
}
Display the user_code and direct the user to visit verification_url. The server polls for completion automatically.
3. Flow Completed (same as browser mode)
Error Handling
If authentication fails:
{
"jsonrpc": "2.0",
"method": "event",
"params": {
"type": "auth.flow.failed",
"timestamp": "2026-03-04T12:00:30.000Z",
"payload": {
"provider": "openai",
"message": "User cancelled authorization"
}
}
}
The original request will receive an error response:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32603,
"message": "User cancelled authorization",
"data": {
"reason": "internal_error"
}
}
}
Antigravity OAuth
Initiating the Flow
{
"jsonrpc": "2.0",
"id": 2,
"method": "auth.connect.antigravity"
}
No parameters required.
Event Sequence
1. Flow Started
{
"jsonrpc": "2.0",
"method": "event",
"params": {
"type": "auth.flow.started",
"timestamp": "2026-03-04T12:01:00.000Z",
"payload": {
"provider": "antigravity"
}
}
}
2. Browser URL Available
{
"jsonrpc": "2.0",
"method": "event",
"params": {
"type": "auth.flow.url",
"timestamp": "2026-03-04T12:01:01.000Z",
"payload": {
"provider": "antigravity",
"url": "https://api.anti-gravity.ai/oauth/authorize?..."
}
}
}
3. Flow Completed
{
"jsonrpc": "2.0",
"method": "event",
"params": {
"type": "auth.flow.completed",
"timestamp": "2026-03-04T12:01:30.000Z",
"payload": {
"provider": "antigravity",
"profile": {
"email": "[email protected]",
"name": "User Name"
}
}
}
}
API Key Authentication
For OpenRouter and Exa, use direct API key methods (no OAuth events):
OpenRouter
{
"jsonrpc": "2.0",
"id": 3,
"method": "auth.set.openrouter_key",
"params": {
"api_key": "sk-or-..."
}
}
Exa
{
"jsonrpc": "2.0",
"id": 4,
"method": "auth.set.exa_key",
"params": {
"api_key": "exa_..."
}
}
Checking Auth Status
{
"jsonrpc": "2.0",
"id": 5,
"method": "auth.status"
}
Response:
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"openai": {
"connected": true,
"account_id": "org-abc123"
},
"antigravity": {
"connected": false
},
"openrouter": {
"connected": true,
"key_set": true
},
"exa": {
"connected": false
}
}
}
Implementation Details
From src/rpc/stdio-server.ts:18-19 and src/core/runtime.ts:2414:
const runtime = await LoafCoreRuntime.create({ rpcMode: true });
// When rpcMode is true:
openBrowser: !this.rpcMode // false in RPC mode
This prevents automatic browser opening and triggers event emission instead.