Skip to content

feat(onboard): enable live model discovery for Copilot#69

Merged
yacosta738 merged 3 commits into
mainfrom
feat/copilot-live-model-discovery
Feb 22, 2026
Merged

feat(onboard): enable live model discovery for Copilot#69
yacosta738 merged 3 commits into
mainfrom
feat/copilot-live-model-discovery

Conversation

@yacosta738
Copy link
Copy Markdown
Contributor

@yacosta738 yacosta738 commented Feb 22, 2026

Live mode selection for GitHub copilot

Summary by CodeRabbit

  • New Features

    • Added GitHub Copilot as a supported live-model provider, enabling discovery and use of Copilot models.
    • Added GitHub token-based authentication and automatic environment-variable resolution for Copilot access.
  • Tests

    • Added tests validating Copilot model discovery, requirement of a non-empty token, and environment-variable key resolution.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Copilot live-model integration
clients/agent-runtime/src/onboard/wizard.rs
Introduces Copilot constants (COPILOT_TOKEN_URL, COPILOT_DEFAULT_API, COPILOT_DISCOVERY_HEADERS), new structs (CopilotApiEndpoints, CopilotApiKeyInfo), fetch_copilot_models() and resolve_live_model_api_key() helpers, extends supports_live_model_fetch and fetch_live_models_for_provider to include "copilot", and adds unit tests for token requirements and key resolution.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description is minimal and lacks required sections like Summary, Tested Information, and Breaking Changes from the template, though the title and commit message provide adequate context. Expand the description with Summary section explaining motivation for the change, Tested Information documenting verification steps, and confirm no breaking changes. Reference issue #69 if applicable.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main feature being added: live model discovery for Copilot, matching the core purpose of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/copilot-live-model-discovery

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 22, 2026

✅ Contributor Report

User: @yacosta738
Status: Passed (12/13 metrics passed)

Metric Description Value Threshold Status
PR Merge Rate PRs merged vs closed 89% >= 30%
Repo Quality Repos with ≥100 stars 0 >= 0
Positive Reactions Positive reactions received 9 >= 1
Negative Reactions Negative reactions received 0 <= 5
Account Age GitHub account age 3040 days >= 30 days
Activity Consistency Regular activity over time 108% >= 0%
Issue Engagement Issues with community engagement 0 >= 0
Code Reviews Code reviews given to others 363 >= 0
Merger Diversity Unique maintainers who merged PRs 3 >= 0
Repo History Merge Rate Merge rate in this repo 88% >= 0%
Repo History Min PRs Previous PRs in this repo 49 >= 0
Profile Completeness Profile richness (bio, followers) 90 >= 0
Suspicious Patterns Spam-like activity detection 1 N/A

Contributor Report evaluates based on public GitHub activity. Analysis period: 2025-02-22 to 2026-02-22

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_TOKEN not consulted for Copilot model discovery, causing a silent no-op for GitHub CLI users

fetch_live_models_for_provider resolves an empty api_key by reading the env var returned by provider_env_var(provider_name), which for "copilot" is GITHUB_TOKEN. However, the setup_provider wizard (line 1741) explicitly detects both GITHUB_TOKEN and GH_TOKEN (the env var set by the GitHub CLI). A user who authenticated only with the GitHub CLI (gh auth login) will have GH_TOKEN set but not GITHUB_TOKEN; api_key will be empty here, and fetch_copilot_models will 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.

Comment on lines +60 to +68
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"),
];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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:

  1. Confirm the legal/ToS posture of using copilot_internal in a third-party CLI agent.
  2. 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.

Comment thread clients/agent-runtime/src/onboard/wizard.rs Outdated
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Feb 22, 2026

Deploying corvus-plugins with  Cloudflare Pages  Cloudflare Pages

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

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Feb 22, 2026

Deploying corvus with  Cloudflare Pages  Cloudflare Pages

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

View logs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread clients/agent-runtime/src/onboard/wizard.rs
Comment thread clients/agent-runtime/src/onboard/wizard.rs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
clients/agent-runtime/src/onboard/wizard.rs (2)

1990-1995: has_api_key guard now correctly includes GH_TOKEN — addresses prior review concern.

Using resolve_live_model_api_key(provider_name, &api_key).is_some() ensures GH_TOKEN is detected for Copilot users during the wizard, matching the behaviour of the actual fetch path.

Note: resolve_live_model_api_key is called twice for the live-fetch path (once here, once inside fetch_live_models_for_provider at 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_TOKEN fallback silently skipped for Copilot aliases (e.g. "github-copilot")

The "copilot" string comparison on line 1037 is not guarded by canonical_provider_name, so any raw alias ("github-copilot") misses the GH_TOKEN fallback even though provider_env_var internally canonicalizes correctly. Current call sites happen to pass canonical names, but run_models_refresh can forward an uncanonicalised default_provider value, 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.

@yacosta738 yacosta738 merged commit ff48634 into main Feb 22, 2026
15 checks passed
@yacosta738 yacosta738 deleted the feat/copilot-live-model-discovery branch February 22, 2026 22:22
@yacosta738 yacosta738 mentioned this pull request Mar 16, 2026
This was referenced Apr 19, 2026
This was referenced Apr 29, 2026
@dallay-bot dallay-bot Bot mentioned this pull request May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant