fix(copilot): use GitHub App token flow for session token exchange#19350
fix(copilot): use GitHub App token flow for session token exchange#19350ben-vargas wants to merge 1 commit intoanomalyco:devfrom
Conversation
Two issues prevented preview models (e.g. claude-opus-4.6-fast) from working through GitHub Copilot: 1. The OAuth device flow used an OAuth App client ID (Ov23li8tweQw6odWQebz) which produces gho_ tokens. The Copilot session token exchange endpoint (/copilot_internal/v2/token) only accepts GitHub App user tokens (ghu_). Switch to the Copilot CLI GitHub App (Iv1.b507a08c87ecfe98) which produces ghu_ tokens that can be exchanged. 2. The raw OAuth token was sent directly as the Bearer token in API requests. Preview models require a proper Copilot session token (tid=...) obtained via the token exchange endpoint. Add a token exchange step in the fetch wrapper that calls /copilot_internal/v2/token to obtain a short-lived session token. The session token is cached in the auth store and refreshed 5 minutes before expiry. Legacy gho_ tokens gracefully fall back to direct use (GA models continue to work, preview models will show a clear API error). Note: existing users with gho_ tokens will need to re-authenticate via the device flow to get a ghu_ token for full preview model support. Fixes anomalyco#19338
|
The following comment was made by an LLM, it may be inaccurate: Found potential related PRs:
These PRs may be related to the same token exchange/session token functionality. You should review them to confirm whether they're addressing the same root cause or if they're separate fixes that complement each other. |
| // Add a small safety buffer when polling to avoid hitting the server | ||
| // slightly too early due to clock skew / timer drift. | ||
| const OAUTH_POLLING_SAFETY_MARGIN_MS = 3000 // 3 seconds | ||
| const CLIENT_ID = "Iv1.b507a08c87ecfe98" |
There was a problem hiding this comment.
No u cannot do this, this would violate the terms of our partnership with github copilot. We must use our id, not theirs.
|
@rekram1-node - I guess the question would be... has Anomoly registered as a GitHub App (which I believe produces ghu_ tokens that the exchange endpoint might accept for token exchange), or does it want to work with GitHub to get the existing OAuth App tokens whitelisted for the exchange endpoint? Probably the broader question... does GitHub intentionally lock the "preview" models to their CLI and SDK only, or will they allow opencode users to be able to use their sub with preview models? If not possible for Anomoly, or GitHub doesn't want opencode users to hit "preview" models, then I think this PR and the related issue can be closed. I'm just not sure if this is a purposeful block or incidental implementation byproduct. |
|
And of course, if there's an Anomaly Client ID that does allow token exchange, absolutely happy to swap that in! I just couldn't get a token exchange to work with Anomaly's ID, and just not sure if that is a GitHub App vs OAuth registration issue, GitHub purposeful block, or what. But, feel free the close if most appropriate path. |
|
Also, just saw #12258 (comment) which may confirm this is intentional and not allowed/possible in which case this PR can be closed. |
|
Per discussion in #19338
If that's the only one Anomaly has, it does not work for exchanging |
Issue for this PR
Closes #19338
Type of change
What does this PR do?
Preview models (like
claude-opus-4.6-fast) fail withmodel_not_supportedbecause the copilot plugin sends the raw OAuth token straight to the API. Three things required changing:Ov23) which producesgho_tokens... the token exchange endpoint rejects those. Switched to the Copilot CLI GitHub App (Iv1.b507a08c87ecfe98) which producesghu_tokens that can actually be exchanged.GET /copilot_internal/v2/tokento get a propertid=...session token. Cached in auth store, refreshes 5 min before expiry.Editor-Version/Copilot-Integration-Idheaders on API requests; session tokens are scoped to an integration and the API 403s without them.Also handled
refresh_token/expires_infrom the device flow in case the app enables token expiration. The refresh/exchange path is serialized withLock.write()following exiting project patterns to prevent concurrent requests from racing on single-use refresh tokens, and rotated refresh tokens are persisted immediately to avoid loss on subsequent failures. Legacygho_users skip the exchange entirely and fall back to direct token use (GA models keep working).Of course, this may be purposefully configured today in agreement with Microsoft/GitHub, if this goes beyond "what is allowed" then feel free to close this PR and related Issue. However, if this is accepted or maintainers make similar code changes based on this PR, opencode users can then use preview models with their GitHub Copilot sub.
How did you verify your code works?
Tested each failure mode with curl against the Copilot API (details in #19338). Built locally, re-authed, confirmed
claude-opus-4.6-fastnow works. All 1529 unit tests pass.Screenshots / recordings
N/A — no UI changes.
Checklist