Skip to content
GitHubBuy Me A Coffee

Provider auth

Obsilo supports Anthropic, OpenAI, GitHub Copilot, Kilo Gateway, Azure, OpenRouter, Ollama, LM Studio, and custom OpenAI-compatible endpoints. Each provider has different authentication requirements, but the agent doesn't care. It talks to a single ApiHandler interface.

The factory

The buildApiHandler factory (src/api/index.ts) takes a provider configuration and returns the right implementation. Anthropic gets its own provider class. GitHub Copilot and Kilo Gateway each have dedicated classes because their auth flows are non-standard. Everything else (OpenAI, Azure, OpenRouter, Ollama, LM Studio, custom endpoints) goes through OpenAiProvider, since they all speak the OpenAI API format.

The factory uses an exhaustive switch. Add a new provider type to the union and TypeScript forces you to handle it.

Standard auth

Most providers use API key authentication. You paste your key in settings, it gets stored, and every request includes it as a Bearer token. The OpenAI-compatible providers (Ollama, LM Studio, OpenRouter, Azure, custom) all work this way, with minor variations in base URL and header format.

Ollama and LM Studio are local providers that run on your machine and don't need an API key at all. The OpenAiProvider handles this by making the key optional when the base URL points to localhost. All HTTP requests go through Obsidian's requestUrl API rather than native fetch, which keeps the plugin compliant with Obsidian's review requirements.

GitHub Copilot: three-stage token chain

GitHub Copilot authentication is more involved. The GitHubCopilotAuthService (src/core/security/GitHubCopilotAuthService.ts) implements a three-stage flow:

  1. Device code flow. The service requests a device code from GitHub, then shows you a URL and a short code. You open the URL in a browser, enter the code, and authorize the application. The service polls GitHub until authorization completes.

  2. Access token. Once authorized, GitHub returns a long-lived access token (valid ~30 days). This token is stored securely and used to obtain short-lived Copilot tokens.

  3. Copilot token. The access token is exchanged for a Copilot-specific token (valid ~1 hour) that's sent with each API request. When it expires, the service automatically refreshes it using the access token. You don't notice the refresh.

The custom fetch wrapper (getCopilotFetch()) is injected into the OpenAI SDK for streaming chat completions. This is necessary because the SDK's built-in fetch doesn't handle Copilot's token format. The wrapper also transparently handles token expiry: if a request fails with a 401, it triggers a refresh and retries.

You can provide a custom GitHub OAuth client ID in settings for enterprise GitHub instances. The default client ID targets github.com.

Kilo Gateway: device auth + manual token

The KiloAuthService (src/core/security/KiloAuthService.ts) supports two auth modes. The device authorization flow works similarly to GitHub Copilot: you get a code, authorize in a browser, and the service polls until complete. Alternatively, you can paste an API token directly for simpler setups.

Both modes produce the same session state. The service stores user profile information and provider defaults (available models, rate limits) retrieved from the gateway API at https://api.kilo.ai/api.

Encrypted storage

API keys and tokens are sensitive credentials. On desktop, the SafeStorageService (src/core/security/SafeStorageService.ts) uses Electron's safeStorage API to encrypt credentials before storing them. This uses the operating system's keychain (Keychain on macOS, Credential Manager on Windows, libsecret on Linux).

The service loads Electron via dynamic require('electron'), one of the few places where require() is allowed instead of ES imports, because Electron can only be loaded dynamically in the renderer process.

On mobile, Electron isn't available. Credentials fall back to Obsidian's standard plugin data storage. This is less secure than OS-level encryption, but mobile Obsidian doesn't expose a keychain API.

Concurrency

Both the Copilot and Kilo auth services include concurrency guards. If multiple requests trigger a token refresh simultaneously, only one refresh runs. The others wait for the same promise. This prevents duplicate auth requests and race conditions during high-frequency API usage.

Adding a new provider

To add a provider that speaks the OpenAI API format: add the type to the LLMProvider union in src/types/settings.ts, handle it in the factory switch (it'll route to OpenAiProvider), and add a settings UI entry. If the provider needs a custom auth flow, create a dedicated provider class and auth service.

The relevant source files:

FileWhat it does
src/api/index.tsFactory function, provider routing
src/api/types.tsApiHandler interface, stream types
src/api/providers/anthropic.tsAnthropic SDK integration
src/api/providers/openai.tsOpenAI-compatible provider (handles 6+ providers)
src/api/providers/github-copilot.tsCopilot provider with custom fetch
src/api/providers/kilo-gateway.tsKilo Gateway with device auth
src/core/security/SafeStorageService.tsElectron keychain encryption
src/core/security/GitHubCopilotAuthService.tsThree-stage Copilot auth
src/core/security/KiloAuthService.tsKilo device auth + manual token