Skip to content
This repository was archived by the owner on May 20, 2026. It is now read-only.

Add BYOK/Offline Mode Support for Copilot CLI Chat Sessions#5038

Closed
jimstrang wants to merge 7 commits into
microsoft:mainfrom
jimstrang:jimstrang/byok-copilot-cli
Closed

Add BYOK/Offline Mode Support for Copilot CLI Chat Sessions#5038
jimstrang wants to merge 7 commits into
microsoft:mainfrom
jimstrang:jimstrang/byok-copilot-cli

Conversation

@jimstrang
Copy link
Copy Markdown

@jimstrang jimstrang commented Apr 7, 2026

Add BYOK/Offline Mode Support for Copilot CLI Chat Sessions

For microsoft/vscode#308269

Enable users to connect Copilot CLI to custom model providers (e.g., Azure OpenAI, local Ollama) via extension settings or environment variables, and operate without GitHub authentication when configured in offline mode.

Motivation

The standalone Copilot CLI already supports BYOK (Bring Your Own Key) for custom model providers. This change brings that same support into the VS Code extension's Copilot CLI integration, achieving parity with the standalone CLI. Configuration is primarily via extension settings (github.copilot.chat.cli.provider.*), with environment variables as a fallback for CLI parity.

What Changed

New: Extension Settings for BYOK

  • configurationService.ts — 12 new ConfigKey.Advanced.CLI* settings for provider configuration (baseUrl, type, wireApi, apiKey, bearerToken, model, modelLimitsId, maxPromptTokens, maxOutputTokens, azureApiVersion) plus offline mode toggle.
  • package.json / package.nls.json — 12 new settings under github.copilot.chat.cli.* with descriptions noting which COPILOT_PROVIDER_* env var each overrides.

Environment Configuration Module

  • copilotCliEnv.ts — Centralized helpers for detecting BYOK/offline mode, extracting provider config, model name, and token limits. All functions take IConfigurationService (required) and check extension settings first, falling back to environment variables (COPILOT_PROVIDER_*, COPILOT_MODEL, COPILOT_OFFLINE). Uses isConfigured() for numeric settings to avoid 0 default value issues.

BYOK Provider Injection

  • copilotCli.ts — When BYOK is configured, constructs a synthetic model entry instead of querying the GitHub API. The display name includes a (BYOK) suffix for user visibility. CopilotCLIModels now accepts IConfigurationService and invalidates its model cache when BYOK settings change (no restart required to switch models).
  • copilotcliSessionService.ts — Injects ProviderConfig into SessionOptions.provider when BYOK is active, routing the SDK to the custom endpoint. Logs provider config with redacted secrets.

Authentication Bypass

Offline Mode

  • mcpHandler.ts — Skips GitHub MCP server setup when offline mode is enabled via setting or COPILOT_OFFLINE=true.

Tests

  • copilotCliModels.spec.ts — Tests synthetic BYOK model construction, custom token limits, and missing model returning empty array.
  • mcpHandler.spec.ts — Tests loadMcpConfig() returns empty config in offline mode (both env var and setting).
  • copilotCliAuth.spec.ts — Verifies BYOK auth bypass and empty-token validation guard.
  • copilotCliEnv.spec.ts — Validates offline mode, provider config building, and extension settings overriding env vars.
  • testHelpers.ts — Shared createMockConfigService() helper used across all BYOK test files.

Design Notes

  • Settings take precedence over env vars: Extension settings (github.copilot.chat.cli.provider.*) are checked first. Environment variables serve as a fallback for CLI parity and for users who configure both tools from their shell profile.
  • Live model refresh: Changing the model or provider base URL in settings invalidates the model cache via onDidChangeConfiguration — no restart needed. Provider config (baseUrl, apiKey, type) takes effect on the next new session.
  • isConfigured() for numeric settings: VS Code returns 0 for unconfigured integer settings with no default. We use isConfigured() to distinguish "user set 0" from "not configured" and fall through to env vars correctly. String settings use || which handles empty/undefined correctly.
  • Empty-token placeholder: getAuthInfo() returns { token: '' } in BYOK mode as a non-null placeholder for callers. The SDK never receives this value — setAuthInfo() is skipped, and BYOK routing is handled entirely by SessionOptions.provider.
  • Redacted diagnostic logging: Provider config is logged at session creation with API keys/bearer tokens replaced by ***.
  • Known limitation: Changing provider settings (baseUrl, apiKey, type) mid-session does not affect active sessions — changes take effect on the next new chat session. This matches the standalone CLI's behavior where env vars are fixed for the session lifetime.

jimstrang and others added 2 commits April 6, 2026 19:15
Enable Copilot CLI to use custom model providers (BYOK) via
COPILOT_PROVIDER_* environment variables, matching the standalone
CLI's env var contract.

Changes:
- Add copilotCliEnv.ts: centralized env helpers for BYOK mode
  detection, provider config construction, offline mode, model
  name, and token limit parsing
- Build synthetic model entry from COPILOT_MODEL in BYOK mode,
  bypassing GitHub model API (copilotCli.ts)
- Skip GitHub token validation in BYOK mode, returning empty
  token auth so the SDK uses the provider config instead
  (copilotCli.ts)
- Guard setAuthInfo to avoid overwriting SDK provider state in
  BYOK mode (copilotcliSession.ts)
- Inject ProviderConfig into SessionOptions when BYOK is active
  (copilotcliSessionService.ts)
- Skip built-in GitHub MCP server setup in offline mode
  (mcpHandler.ts)
- Bypass empty-token auth failure in BYOK mode for both chat
  session participant variants (copilotCLIChatSessions.ts,
  copilotCLIChatSessionsContribution.ts)
- Add BYOK auth unit test (copilotCliAuth.spec.ts)
- Add offline mode env parsing tests (copilotCliEnv.spec.ts)
- Append (BYOK) suffix to model display name for user visibility
Copilot AI review requested due to automatic review settings April 7, 2026 12:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds BYOK (custom provider) and offline-mode support to the Copilot CLI chat session integration inside the VS Code Copilot Chat extension, enabling operation without GitHub auth when configured via environment variables (parity with standalone Copilot CLI).

Changes:

  • Introduces a centralized environment parsing module for BYOK/offline settings (COPILOT_PROVIDER_*, COPILOT_MODEL, COPILOT_OFFLINE).
  • Enables BYOK routing by injecting SessionOptions.provider, synthesizing a model entry from env vars, and bypassing empty-token auth validation.
  • Adds offline mode behavior to skip GitHub MCP server setup, plus unit tests for env/offline parsing and BYOK auth bypass.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/extension/chatSessions/vscode-node/copilotCLIChatSessionsContribution.ts Allows empty-token auth when BYOK env is set (avoids forcing GitHub sign-in).
src/extension/chatSessions/vscode-node/copilotCLIChatSessions.ts Same BYOK empty-token authorization bypass in the non-contribution participant path.
src/extension/chatSessions/copilotcli/node/copilotCliEnv.ts New env parsing helpers for BYOK provider config, model, token limits, and offline mode.
src/extension/chatSessions/copilotcli/node/copilotCli.ts Synthesizes BYOK model list from env; bypasses token validation when BYOK is active.
src/extension/chatSessions/copilotcli/node/copilotcliSessionService.ts Injects BYOK ProviderConfig into SessionOptions.provider.
src/extension/chatSessions/copilotcli/node/copilotcliSession.ts Avoids calling setAuthInfo() in BYOK mode during model updates.
src/extension/chatSessions/copilotcli/node/mcpHandler.ts Skips built-in GitHub MCP server wiring when COPILOT_OFFLINE=true.
src/extension/chatSessions/copilotcli/node/test/copilotCliAuth.spec.ts Adds a test ensuring BYOK mode returns empty-token auth without calling GitHub auth.
src/extension/chatSessions/copilotcli/node/test/copilotCliEnv.spec.ts Adds tests for COPILOT_OFFLINE parsing behavior.

Comment thread src/extension/chatSessions/copilotcli/node/copilotCli.ts
Comment thread src/extension/chatSessions/copilotcli/node/copilotCli.ts Outdated
Comment thread src/extension/chatSessions/copilotcli/node/copilotcliSession.ts Outdated
Comment thread src/extension/chatSessions/copilotcli/node/mcpHandler.ts
Comment thread src/extension/chatSessions/copilotcli/node/test/copilotCliEnv.spec.ts Outdated
jimstrang and others added 3 commits April 7, 2026 07:16
- Use [CopilotCLIModels] prefix for logs in _getAvailableModels() and
  [CopilotCLISDK] for logs in getAuthInfo() to match emitting class
- Save/restore COPILOT_OFFLINE in copilotCliEnv.spec.ts afterEach()
  to avoid leaking env state across test runs
- copilotCliModels: test synthetic BYOK model construction, custom token
  limits, and missing COPILOT_MODEL returning empty array
- copilotCliEnv: test getCopilotByokProvider() with all env var
  combinations including Azure config
- mcpHandler: test loadMcpConfig() returns empty config in offline mode
- copilotCliAuth: test empty-token auth passes validation guard when
  BYOK mode is active
@jimstrang jimstrang force-pushed the jimstrang/byok-copilot-cli branch from 7baabcc to 862836a Compare April 7, 2026 12:59
@DonJayamanne
Copy link
Copy Markdown
Collaborator

@jimstrang thank you for this PR,
Currently I'm not keen on the approach of using env variables to light up this feature, this generally isn't how we extend VS Code/Extensions, I will discuss this and get back to you.

@jimstrang
Copy link
Copy Markdown
Author

Thanks @DonJayamanne. I'm with you, I don't particularly like it either - was just matching what the Copilot CLI expects which is those specific env vars. I'm personally not picky about how to flag it on the extension side. Info on the env vars is available through the cli COPILOT_PROVIDER_HELP=1 copilot help environment.

Let me know if I can help!

@jimstrang
Copy link
Copy Markdown
Author

@DonJayamanne I put some thought into this today. I think that it would be feasible to add extention settings for the CLI BYOK config which would naturally take presedence over any set environment variables that could be in use with Copilot CLI outside of Copilot Chat.

If you think that's a good path I can spend a few days working through the code.

- Add 12 new settings under github.copilot.chat.cli.provider.* and
  github.copilot.chat.cli.offline for configuring custom model providers
- Settings take precedence over COPILOT_PROVIDER_* environment variables;
  env vars serve as fallback for standalone CLI parity
- Replace Lazy<> model cache with invalidation on settings change so
  model switching takes effect without restart
- Add IConfigurationService to CopilotCLIModels and all BYOK helper
  functions (required parameter, matching codebase conventions)
- Use isConfigured() for numeric settings to avoid VS Code returning 0
  for unconfigured integer defaults
- Redact API keys and bearer tokens in diagnostic logs
- Add createMockConfigService() shared test helper to reduce mock
  duplication across test files
- Add settings-override tests verifying precedence over env vars
@jimstrang
Copy link
Copy Markdown
Author

Addressed the concern about using environment variables to configure this feature (262c07d).

What changed: BYOK configuration is now driven by extension settings under github.copilot.chat.cli.provider.* (12 new settings), which is the standard approach for VS Code extensions. Environment variables are retained only as a fallback — they kick in when no extension setting is configured, preserving compatibility with the standalone Copilot CLI (which uses the same COPILOT_PROVIDER_* env vars). Settings always take precedence.

Other improvements in this push:

  • Model list refreshes live when BYOK settings change — no restart required to switch models
  • API keys and bearer tokens are redacted in diagnostic logs
  • Shared createMockConfigService() test helper replaces scattered inline mocks
  • isConfigured() guard for numeric settings to avoid VS Code's 0 default for unconfigured integers
  • Added tests verifying settings override env vars

Known limitation: Changing provider settings (baseUrl, apiKey, type) mid-session does not affect active sessions — changes take effect on the next new chat. This matches the standalone CLI's behavior.

Code defaults must exactly match package.json defaults per the
Configuration Defaults validation test. String settings default to
empty string, integer settings default to 0, matching VS Code's
inferred defaults for these schema types.
@KevinDMack
Copy link
Copy Markdown

Team, are there any further updates on this release? This is critical to some of our government customers.

@digitarald
Copy link
Copy Markdown
Contributor

Continued in microsoft/vscode#312207 , thanks for the PR, @jimstrang .

We probably won't use the env variables in the first take, as our BYOK is driven by extension contibutions. But I am curious to learn how we can provide the right configuration governance.

@digitarald digitarald closed this Apr 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants