Skip to content

feat(provider): add RFC 8628 device-flow OAuth for custom gateways - #34785

Closed
fijimunkii wants to merge 1 commit into
anomalyco:devfrom
fijimunkii:feat/custom-oauth-device-flow
Closed

fijimunkii wants to merge 1 commit into
anomalyco:devfrom
fijimunkii:feat/custom-oauth-device-flow

Conversation

@fijimunkii

@fijimunkii fijimunkii commented Jul 1, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes # (no associated issue — opening as a feature contribution per CONTRIBUTING.md)

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Adds a generic RFC 8628 device-flow OAuth provider type so any custom OpenAI-compatible gateway can expose browser-based SSO with the same UX as built-in providers (xAI, Copilot).

Users declare deviceAuthorizationUrl + tokenUrl under a provider's options block in opencode.json. opencode detects these and auto-registers a device-flow plugin for that provider — no CLI changes, no per-gateway code.

{
  "provider": {
    "my-gateway": {
      "npm": "@ai-sdk/openai-compatible",
      "options": {
        "baseURL": "https://my-gateway.example.com/v1",
        "deviceAuthorizationUrl": "https://my-gateway.example.com/oauth/device_authorization",
        "tokenUrl": "https://my-gateway.example.com/oauth/token"
      }
    }
  }
}

Optional: options.oauthClientId (default "opencode"), options.oauthScope (omitted from POST body when unset).

Implementation:

  • New packages/opencode/src/plugin/oauth-device.ts — parameterized factory OAuthDeviceProviderPlugin porting device-code + refresh logic from xai.ts. Proactive refresh with 120s skew, single-flight per provider instance via module-level refreshPromises map, rotated tokens persisted to auth.json via input.client.auth.set.
  • packages/opencode/src/plugin/index.ts — internalPlugins() now takes cfg and derives one plugin instance per config entry that has both deviceAuthorizationUrl and tokenUrl.
  • packages/core/src/v1/config/provider.ts — four optional fields on options: deviceAuthorizationUrl, tokenUrl, oauthClientId, oauthScope.
  • packages/web/src/content/docs/providers.mdx — new "Custom OAuth (device flow)" section.

Requirements on the OAuth server side: standard RFC 8628 endpoints (device_authorization returns device_code/user_code/verification_uri; token endpoint accepts grant_type=urn:ietf:params:oauth:grant-type:device_code, handles authorization_pending/slow_down/access_denied/expired_token, returns access_token + refresh_token). token_type SHOULD be Bearer.

How did you verify your code works?

  • cd packages/opencode && bun test test/plugin/oauth-device.test.ts — 24/24 pass. Covers requestDeviceCode (default client_id, custom client_id, scope omission, error surfacing), pollDeviceCodeToken (success, slow_down/authorization_pending backoff, terminal errors, timeout, NaN-interval defense), and loader behavior (non-oauth passthrough, dummy bearer injection, header override, auth-type flip, proactive refresh + persistence, single-flight dedup, cross-instance isolation).
  • cd packages/opencode && bun test test/plugin/xai.test.ts — 26/26 pass (regression).
  • bun turbo typecheck --force --filter=@opencode-ai/core --filter=@opencode-ai/server — clean.
  • End-to-end smoke against an internal LiteLLM gateway exposing RFC 8628 endpoints: opencode auth login completes the browser flow, access token is injected as Authorization: Bearer … on gateway requests, proactive refresh fires before expiry, and chat completions succeed.

Screenshots / recordings

N/A — no UI changes. Login flow reuses the existing device-flow picker.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions github-actions Bot added needs:compliance This means the issue will auto-close after 2 hours. and removed needs:compliance This means the issue will auto-close after 2 hours. labels Jul 1, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

Custom OpenAI-compatible providers can now declare RFC 8628 device-flow
OAuth endpoints under `options` and get the same browser-based SSO UX
that built-in providers (xAI, Copilot) already provide. opencode prints
a verification URL + user code, polls the token endpoint, and persists
the resulting access/refresh token pair to auth.json. Refresh is
proactive (120s skew) and single-flight per provider instance.

Config:

```json
{
  "provider": {
    "my-gateway": {
      "npm": "@ai-sdk/openai-compatible",
      "options": {
        "baseURL": "https://my-gateway.example.com/v1",
        "deviceAuthorizationUrl": "https://my-gateway.example.com/oauth/device_authorization",
        "tokenUrl": "https://my-gateway.example.com/oauth/token"
      }
    }
  }
}
```

Optional fields: `oauthClientId` (default "opencode"), `oauthScope`
(omitted from POST body when unset).

Implementation ports the device-code + refresh logic from `xai.ts` into
a parameterized `oauth-device.ts` plugin factory. `internalPlugins()`
derives one plugin instance per config entry that has both
`deviceAuthorizationUrl` and `tokenUrl`.

Tested end-to-end against an internal LiteLLM gateway exposing RFC 8628
endpoints.

Localized docs (17 translated providers.mdx copies) intentionally
skipped — no translation workflow exists in-repo; happy to follow up
if maintainers want them.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
@fijimunkii
fijimunkii force-pushed the feat/custom-oauth-device-flow branch from 4de37d9 to 18e1512 Compare July 1, 2026 19:32
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant