Add BYOK/Offline Mode Support for Copilot CLI Chat Sessions#5038
Add BYOK/Offline Mode Support for Copilot CLI Chat Sessions#5038jimstrang wants to merge 7 commits into
Conversation
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
There was a problem hiding this comment.
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. |
- 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
7baabcc to
862836a
Compare
|
@jimstrang thank you for this PR, |
|
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 Let me know if I can help! |
|
@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
|
Addressed the concern about using environment variables to configure this feature (262c07d). What changed: BYOK configuration is now driven by extension settings under Other improvements in this push:
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.
|
Team, are there any further updates on this release? This is critical to some of our government customers. |
|
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. |
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 newConfigKey.Advanced.CLI*settings for provider configuration (baseUrl,type,wireApi,apiKey,bearerToken,model,modelLimitsId,maxPromptTokens,maxOutputTokens,azureApiVersion) plusofflinemode toggle.package.json/package.nls.json— 12 new settings undergithub.copilot.chat.cli.*with descriptions noting whichCOPILOT_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 takeIConfigurationService(required) and check extension settings first, falling back to environment variables (COPILOT_PROVIDER_*,COPILOT_MODEL,COPILOT_OFFLINE). UsesisConfigured()for numeric settings to avoid0default 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.CopilotCLIModelsnow acceptsIConfigurationServiceand invalidates its model cache when BYOK settings change (no restart required to switch models).copilotcliSessionService.ts— InjectsProviderConfigintoSessionOptions.providerwhen BYOK is active, routing the SDK to the custom endpoint. Logs provider config with redacted secrets.Authentication Bypass
copilotCli.ts,copilotcliSession.ts,copilotCLIChatSessions.ts,copilotCLIChatSessionsContribution.ts— In BYOK mode,getAuthInfo()returns an empty-token placeholder so callers that expect a non-null result don't crash.setAuthInfo()is skipped (guarded by!isCopilotByokMode()), so the SDK never receives this empty token. Token validation checks are also guarded.Offline Mode
mcpHandler.ts— Skips GitHub MCP server setup when offline mode is enabled via setting orCOPILOT_OFFLINE=true.Tests
copilotCliModels.spec.ts— Tests synthetic BYOK model construction, custom token limits, and missing model returning empty array.mcpHandler.spec.ts— TestsloadMcpConfig()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— SharedcreateMockConfigService()helper used across all BYOK test files.Design Notes
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.onDidChangeConfiguration— no restart needed. Provider config (baseUrl, apiKey, type) takes effect on the next new session.isConfigured()for numeric settings: VS Code returns0for unconfiguredintegersettings with no default. We useisConfigured()to distinguish "user set 0" from "not configured" and fall through to env vars correctly. String settings use||which handles empty/undefined correctly.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 bySessionOptions.provider.***.