feat(onboard): enable live model discovery for Copilot#69
Conversation
📝 WalkthroughWalkthroughAdds internal Copilot live-model discovery to the onboarding wizard: resolves Copilot API tokens from a GitHub token, discovers Copilot endpoints, fetches model lists and converts them to OpenAI-compatible IDs, wires "copilot" into live-model fetching, and adds unit tests. No public API changes. Changes
Sequence DiagramsequenceDiagram
participant Client as Wizard Client
participant Resolver as Key Resolver
participant TokenAPI as Copilot Token API
participant ModelsAPI as Copilot Models API
participant Parser as Model Parser
Client->>Resolver: fetch_copilot_models(optional_api_key)
Resolver->>Resolver: resolve GH token (env or provided)
Resolver-->>Client: return gh_token
Client->>TokenAPI: POST discovery request (GH token + discovery headers)
TokenAPI-->>Client: Copilot API token + endpoint info
Client->>ModelsAPI: GET /models (Bearer Copilot token) at resolved endpoint
ModelsAPI-->>Parser: return model list JSON
Parser->>Client: parsed OpenAI-compatible model IDs
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Contributor ReportUser: @yacosta738
Contributor Report evaluates based on public GitHub activity. Analysis period: 2025-02-22 to 2026-02-22 |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
clients/agent-runtime/src/onboard/wizard.rs (1)
1071-1088:⚠️ Potential issue | 🟡 Minor
GH_TOKENnot consulted for Copilot model discovery, causing a silent no-op for GitHub CLI users
fetch_live_models_for_providerresolves an emptyapi_keyby reading the env var returned byprovider_env_var(provider_name), which for "copilot" isGITHUB_TOKEN. However, thesetup_providerwizard (line 1741) explicitly detects bothGITHUB_TOKENandGH_TOKEN(the env var set by the GitHub CLI). A user who authenticated only with the GitHub CLI (gh auth login) will haveGH_TOKENset but notGITHUB_TOKEN;api_keywill be empty here, andfetch_copilot_modelswill return the "GitHub token is required" error despite the user having a valid token.The pattern used for Anthropic (falling back to
ANTHROPIC_OAUTH_TOKEN) shows the intended approach:🔧 Suggested fix: add a `GH_TOKEN` fallback for the copilot arm
let api_key = if api_key.trim().is_empty() { std::env::var(provider_env_var(provider_name)) .ok() .or_else(|| { // Anthropic also accepts OAuth setup-tokens via ANTHROPIC_OAUTH_TOKEN if provider_name == "anthropic" { std::env::var("ANTHROPIC_OAUTH_TOKEN").ok() + } else if provider_name == "copilot" { + // GitHub CLI sets GH_TOKEN; accept either + std::env::var("GH_TOKEN").ok() } else { None } })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/agent-runtime/src/onboard/wizard.rs` around lines 1071 - 1088, fetch_live_models_for_provider currently only falls back to the env var returned by provider_env_var(provider_name) (e.g., "GITHUB_TOKEN" for copilot) and thus misses GH_TOKEN set by the GitHub CLI; update the empty-api-key branch in fetch_live_models_for_provider to also try std::env::var("GH_TOKEN").ok() when provider_name == "copilot" (analogous to the Anthropic ANTHROPIC_OAUTH_TOKEN fallback), ensure the value is trimmed, converted to Some(String) only if non-empty, and keep the existing map/filter chaining so consumers like fetch_copilot_models will see the GH_TOKEN token.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@clients/agent-runtime/src/onboard/wizard.rs`:
- Around line 60-68: The code currently uses an undocumented/unsupported
endpoint and hardcoded impersonation headers: remove or gate usage of
COPILOT_TOKEN_URL ("https://api.github.com/copilot_internal/v2/token") and the
pinned COPILOT_DISCOVERY_HEADERS, and instead default to the public
COPILOT_DEFAULT_API flow or a configurable, documented backend; update the code
around COPILOT_TOKEN_URL, COPILOT_DEFAULT_API, and COPILOT_DISCOVERY_HEADERS to
(a) stop calling the internal endpoint unless a clearly named opt-in config flag
is set and documented with legal/ToS approval, (b) replace hardcoded
VSCode/Copilot version strings with either dynamic user-agent construction or a
configurable header value with clear update cadence, and (c) add runtime
checks/log messages that surface when the internal endpoint is disabled so
callers fall back gracefully to the public API.
- Around line 75-80: The Copilot bearer token is exposed by deriving Debug on
CopilotApiKeyInfo; remove the unnecessary #[derive(Debug)] from the struct
declaration (or alternatively implement a custom Debug that redacts the token
field) so that the token field on CopilotApiKeyInfo is not printable in logs;
locate the struct CopilotApiKeyInfo and either drop Debug from its derive list
or replace it with a manual impl that omits or masks the token field.
---
Outside diff comments:
In `@clients/agent-runtime/src/onboard/wizard.rs`:
- Around line 1071-1088: fetch_live_models_for_provider currently only falls
back to the env var returned by provider_env_var(provider_name) (e.g.,
"GITHUB_TOKEN" for copilot) and thus misses GH_TOKEN set by the GitHub CLI;
update the empty-api-key branch in fetch_live_models_for_provider to also try
std::env::var("GH_TOKEN").ok() when provider_name == "copilot" (analogous to the
Anthropic ANTHROPIC_OAUTH_TOKEN fallback), ensure the value is trimmed,
converted to Some(String) only if non-empty, and keep the existing map/filter
chaining so consumers like fetch_copilot_models will see the GH_TOKEN token.
| const COPILOT_TOKEN_URL: &str = "https://api.github.com/copilot_internal/v2/token"; | ||
| const COPILOT_DEFAULT_API: &str = "https://api.githubcopilot.com"; | ||
|
|
||
| const COPILOT_DISCOVERY_HEADERS: [(&str, &str); 4] = [ | ||
| ("Editor-Version", "vscode/1.85.1"), | ||
| ("Editor-Plugin-Version", "copilot/1.155.0"), | ||
| ("User-Agent", "GithubCopilot/1.155.0"), | ||
| ("Accept", "application/json"), | ||
| ]; |
There was a problem hiding this comment.
copilot_internal endpoint violates GitHub ToS and is an undocumented unstable API
COPILOT_TOKEN_URL points to https://api.github.com/copilot_internal/v2/token, which is not part of GitHub's public API contract. The endpoint is "intended solely for use through GitHub Copilot's officially supported clients" and using it in other applications "would violate GitHub's Terms of Service and the Copilot license agreement." This exposes users to risk of account suspension and the endpoint can change or disappear without notice.
Additionally, COPILOT_DISCOVERY_HEADERS hardcodes specific VS Code (1.85.1) and Copilot (1.155.0) version strings to impersonate a VS Code session — directly tied to the ToS concern. If GitHub ever enforces version freshness, discovery will silently fail for all users with no actionable error.
These are architectural concerns that should be validated before shipping:
- Confirm the legal/ToS posture of using
copilot_internalin a third-party CLI agent. - Document why the version strings are pinned and at what cadence they will be updated.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@clients/agent-runtime/src/onboard/wizard.rs` around lines 60 - 68, The code
currently uses an undocumented/unsupported endpoint and hardcoded impersonation
headers: remove or gate usage of COPILOT_TOKEN_URL
("https://api.github.com/copilot_internal/v2/token") and the pinned
COPILOT_DISCOVERY_HEADERS, and instead default to the public COPILOT_DEFAULT_API
flow or a configurable, documented backend; update the code around
COPILOT_TOKEN_URL, COPILOT_DEFAULT_API, and COPILOT_DISCOVERY_HEADERS to (a)
stop calling the internal endpoint unless a clearly named opt-in config flag is
set and documented with legal/ToS approval, (b) replace hardcoded VSCode/Copilot
version strings with either dynamic user-agent construction or a configurable
header value with clear update cadence, and (c) add runtime checks/log messages
that surface when the internal endpoint is disabled so callers fall back
gracefully to the public API.
Deploying corvus-plugins with
|
| Latest commit: |
f053bd0
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://d5a54ee8.corvus-plugins.pages.dev |
| Branch Preview URL: | https://feat-copilot-live-model-disc.corvus-plugins.pages.dev |
Deploying corvus with
|
| Latest commit: |
f053bd0
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://319b81b7.corvus-42x.pages.dev |
| Branch Preview URL: | https://feat-copilot-live-model-disc.corvus-42x.pages.dev |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@clients/agent-runtime/src/onboard/wizard.rs`:
- Around line 1026-1046: The has_api_key guard in setup_provider is out of sync
with resolve_live_model_api_key: resolve_live_model_api_key accepts GH_TOKEN for
provider_name "copilot" but setup_provider's has_api_key only checks
provider_env_var("copilot") (GITHUB_TOKEN), so change the has_api_key check in
setup_provider to also consider std::env::var("GH_TOKEN") (or provider_env_var
fallback plus GH_TOKEN) when provider_name == "copilot"; this ensures the wizard
detects GH_TOKEN-authenticated users the same way fetch_copilot_models will.
- Around line 5310-5334: The test
resolve_live_model_api_key_uses_gh_token_for_copilot currently panics while
holding env_lock() and manually restores GITHUB_TOKEN/GH_TOKEN, which can poison
the mutex and leak env state; fix by acquiring the mutex with
env_lock().lock().unwrap_or_else(|e| e.into_inner()) to recover from PoisonError
and introduce a small RAII helper (e.g., EnvRestore) inside the test that
captures previous GITHUB_TOKEN and GH_TOKEN on creation and restores them in
Drop so restoration always runs even if the assertion panics; keep the rest of
the test logic the same but replace the manual restore block with the RAII guard
and use the recovered mutex guard when interacting with environment.
---
Duplicate comments:
In `@clients/agent-runtime/src/onboard/wizard.rs`:
- Around line 60-68: COPILOT_TOKEN_URL and COPILOT_DISCOVERY_HEADERS are still
hardcoding an internal token endpoint and editor/Copilot version strings
(impersonation risk); update the code to stop using the internal endpoint and
baked-in headers: make COPILOT_TOKEN_URL and COPILOT_DISCOVERY_HEADERS
configurable (e.g., via environment/config) and default COPILOT_TOKEN_URL to the
public/approved API (or fall back to COPILOT_DEFAULT_API), remove or neutralize
explicit Editor-Version, Editor-Plugin-Version and User-Agent impersonation
values in COPILOT_DISCOVERY_HEADERS, and ensure any new defaults are compliant
with the service ToS before committing (refer to symbols COPILOT_TOKEN_URL,
COPILOT_DISCOVERY_HEADERS, and COPILOT_DEFAULT_API).
There was a problem hiding this comment.
🧹 Nitpick comments (2)
clients/agent-runtime/src/onboard/wizard.rs (2)
1990-1995:has_api_keyguard now correctly includesGH_TOKEN— addresses prior review concern.Using
resolve_live_model_api_key(provider_name, &api_key).is_some()ensuresGH_TOKENis detected for Copilot users during the wizard, matching the behaviour of the actual fetch path.Note:
resolve_live_model_api_keyis called twice for the live-fetch path (once here, once insidefetch_live_models_for_providerat line 1095). This is harmless but could be eliminated by returning the resolved key from the guard and threading it into the fetch call.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/agent-runtime/src/onboard/wizard.rs` around lines 1990 - 1995, The guard currently calls resolve_live_model_api_key(provider_name, &api_key).is_some() which correctly detects GH_TOKEN but duplicates resolution later in fetch_live_models_for_provider; to clean this up, change the guard to capture the resolved key (e.g., let resolved_key = resolve_live_model_api_key(provider_name, &api_key)) and use that truthiness for the has_api_key check, then pass the resolved_key into fetch_live_models_for_provider (or its equivalent call) instead of calling resolve_live_model_api_key again; keep supports_live_model_fetch(provider_name) and the existing cached-path logic unchanged.
1026-1046:GH_TOKENfallback silently skipped for Copilot aliases (e.g."github-copilot")The
"copilot"string comparison on line 1037 is not guarded bycanonical_provider_name, so any raw alias ("github-copilot") misses theGH_TOKENfallback even thoughprovider_env_varinternally canonicalizes correctly. Current call sites happen to pass canonical names, butrun_models_refreshcan forward an uncanonicaliseddefault_providervalue, breaking discovery silently.♻️ Proposed fix — canonicalize at function entry
fn resolve_live_model_api_key(provider_name: &str, api_key: &str) -> Option<String> { + let provider_name = canonical_provider_name(provider_name); if !api_key.trim().is_empty() { return Some(api_key.trim().to_string()); } std::env::var(provider_env_var(provider_name)) .ok() .or_else(|| { if provider_name == "anthropic" { std::env::var("ANTHROPIC_OAUTH_TOKEN").ok() } else if provider_name == "copilot" { std::env::var("GH_TOKEN").ok() } else { None } }) .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/agent-runtime/src/onboard/wizard.rs` around lines 1026 - 1046, The resolve_live_model_api_key function can miss GH_TOKEN fallback for aliases like "github-copilot" because it compares provider_name directly to "copilot"; canonicalize provider_name at the start (using the same canonical_provider_name helper used by provider_env_var) so the later comparisons (the "copilot" branch) and provider_env_var behave consistently and allow the GH_TOKEN fallback to run for copilot aliases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@clients/agent-runtime/src/onboard/wizard.rs`:
- Around line 60-68: COPILOT_TOKEN_URL and COPILOT_DISCOVERY_HEADERS are using
an internal GitHub endpoint and hardcoded impersonation headers which risks
ToS/account suspension and breakage; change COPILOT_TOKEN_URL to use the
supported public API or make it configurable (e.g., read from env/config with
COPILOT_DEFAULT_API as fallback) and remove or replace COPILOT_DISCOVERY_HEADERS
so it does not impersonate VSCode/Copilot versions—use a generic/official
User-Agent or construct the headers from configurable values (editor
name/version, plugin version) rather than hardcoding values in the
COPILOT_DISCOVERY_HEADERS constant.
---
Nitpick comments:
In `@clients/agent-runtime/src/onboard/wizard.rs`:
- Around line 1990-1995: The guard currently calls
resolve_live_model_api_key(provider_name, &api_key).is_some() which correctly
detects GH_TOKEN but duplicates resolution later in
fetch_live_models_for_provider; to clean this up, change the guard to capture
the resolved key (e.g., let resolved_key =
resolve_live_model_api_key(provider_name, &api_key)) and use that truthiness for
the has_api_key check, then pass the resolved_key into
fetch_live_models_for_provider (or its equivalent call) instead of calling
resolve_live_model_api_key again; keep supports_live_model_fetch(provider_name)
and the existing cached-path logic unchanged.
- Around line 1026-1046: The resolve_live_model_api_key function can miss
GH_TOKEN fallback for aliases like "github-copilot" because it compares
provider_name directly to "copilot"; canonicalize provider_name at the start
(using the same canonical_provider_name helper used by provider_env_var) so the
later comparisons (the "copilot" branch) and provider_env_var behave
consistently and allow the GH_TOKEN fallback to run for copilot aliases.
Live mode selection for GitHub copilot
Summary by CodeRabbit
New Features
Tests