Skip to content

Commit 396aefb

Browse files
feat: promote to well-known provider — zero Node.js, env var auth, retry integration (microsoft#9)
* feat: resolve SDK bundled binary first in mount gate, eliminating Node.js requirement Make _find_copilot_cli() check for the SDK's bundled binary (copilot/bin/copilot) before falling back to system PATH lookup. This eliminates the Node.js requirement when the copilot SDK package is installed with its bundled binary. Search order is now: 1. COPILOT_CLI_PATH environment variable (explicit override) 2. SDK bundled binary (copilot/bin/copilot next to the installed package) 3. System PATH via shutil.which (original fallback) Adds three new tests for SDK binary resolution logic: - test_cli_from_sdk_bundled_binary - test_cli_sdk_binary_preferred_over_path - test_cli_fallback_when_sdk_binary_missing 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * feat: add github_token passthrough with env var precedence chain Added token-based auth support to _build_client_options() so users can authenticate via environment variables (COPILOT_GITHUB_TOKEN, GH_TOKEN, GITHUB_TOKEN) or explicit config instead of needing the npm CLI. Token precedence: config > COPILOT_GITHUB_TOKEN > GH_TOKEN > GITHUB_TOKEN > OAuth fallback. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * feat: advertise credential env vars and improve auth error message Update get_info() to advertise GITHUB_TOKEN, GH_TOKEN, and COPILOT_GITHUB_TOKEN in credential_env_vars so the CLI can detect available authentication paths. Improve _verify_authentication() error message to guide users toward 'Set GITHUB_TOKEN', 'gh auth login', or 'amplifier init' instead of the obsolete 'copilot auth login' command. Task 3 of 8 in the well-known provider promotion plan. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * feat: add auth guidance and model choice config fields for setup wizard Add two ConfigField entries to get_info() so the amplifier init setup wizard can guide users through authentication and model selection: - auth_info: text field explaining GitHub token / browser login options - model: choice field with curated model options (claude-sonnet-4 default) Includes 3 new tests in TestConfigFields verifying field presence, types, and model choices. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * feat: translate Copilot exceptions to kernel LLMError types at complete() boundary - Added kernel LLMError imports (AbortError, AuthenticationError, LLMError, LLMTimeoutError, NetworkError, NotFoundError, ProviderUnavailableError, RateLimitError) to provider.py - Expanded Copilot exception imports (CopilotAbortError, CopilotAuthenticationError, CopilotConnectionError, CopilotModelNotFoundError, CopilotProviderError, CopilotRateLimitError, CopilotSdkLoopError, CopilotSessionError) - Replaced bare except Exception block in complete() with comprehensive error translation mapping each Copilot exception to its kernel equivalent, preserving retryable/retry_after semantics - Added TestErrorTranslation class with 12 tests covering all paths - Updated 3 existing metrics tests to expect KernelLLMError instead of RuntimeError (behavioral change from new error wrapping) 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * feat: add retry_with_backoff wrapper around complete() with rate-limit fail-fast - Add RetryConfig/retry_with_backoff imports from amplifier_core.utils.retry - Initialize _retry_config in __init__() with configurable max_retries, min_delay, max_delay, jitter from provider config - Restructure complete() using inner-function pattern: move session creation, send, and error translation into _do_complete() inner function - Add _on_retry callback that emits provider:retry events - Add rate-limit fail-fast: mark CopilotRateLimitError as non-retryable when retry_after exceeds max_delay - Add TestRetryIntegration class with 3 tests verifying config defaults, custom config, and non-retryable error propagation - Set max_retries=0 in default test provider_config fixture to prevent real asyncio.sleep delays in tests 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * docs: remove Node.js prerequisite, document env var auth paths - Remove Node.js 18+ and npm from prerequisites (SDK binary is now bundled) - Document three auth options: env var (recommended), amplifier init wizard, gh CLI bridge - Update installation to reflect well-known provider status (amplifier init handles everything) - Keep all other sections (Usage, Models, Config, Features, Development, etc.) intact 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * fix: use secret field with env_var for credential detection, remove hardcoded model list This fixes two issues in the GitHub Copilot provider config_fields: - Changed field_type from "text" to "secret" with env_var="GITHUB_TOKEN" so the CLI wizard can detect existing credentials ("Found in environment/keyring") - Removed hardcoded model choice field since the CLI wizard already calls list_models() dynamically for model selection Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * fix: guide users to gh auth login or copilot login instead of pasting tokens Update ConfigField auth prompt to direct users toward 'gh auth login' or 'copilot login' instead of asking them to paste a raw GitHub token. Change display_name to 'GitHub Copilot Authentication' for clarity. Update test assertions to verify the new display_name and prompt content. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * fix: remove auth config field — auth handled by stored OAuth creds or env vars The ConfigField system (text/secret/choice/boolean) cannot check live auth status or launch OAuth flows. Authentication is handled naturally: - Stored OAuth credentials in ~/.copilot/ (from copilot login or gh auth login) - Environment variables (GITHUB_TOKEN, etc.) - Auth check happens when list_models() runs during wizard model selection Changes: - Remove ConfigField import from provider.py (no longer used) - Set config_fields=[] in get_info() - Replace TestConfigFields with single test_config_fields_empty test 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --------- Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
1 parent 1e0734d commit 396aefb

8 files changed

Lines changed: 751 additions & 88 deletions

File tree

README.md

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,50 +7,62 @@ GitHub Copilot SDK integration for Amplifier via Copilot CLI.
77
## Prerequisites
88

99
- **Python 3.11+**
10-
- **Node.js 18+** — Required to install the Copilot CLI
1110
- **GitHub Copilot subscription** — Active Business or Enterprise subscription
1211
- **[UV](https://github.com/astral-sh/uv)** (optional) — Fast Python package manager (pip works too)
1312

14-
### Installing Copilot CLI
13+
> **No Node.js required.** The Copilot SDK binary is bundled with the Python package
14+
> and discovered automatically.
1515
16-
The Copilot CLI is a Node.js binary that the Python SDK controls via JSON-RPC.
17-
Both the CLI and the SDK are required.
16+
## Authentication
1817

19-
```bash
20-
# Install Copilot CLI (requires Node.js/npm)
21-
npm install -g @github/copilot
18+
Set a GitHub token as an environment variable. The provider checks these in order:
19+
`COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_TOKEN`.
20+
21+
### Option 1: Environment variable (recommended)
2222

23-
# Verify installation
24-
copilot --version
23+
```bash
24+
export GITHUB_TOKEN="ghp_your_token_here"
2525
```
2626

27-
### Authentication
27+
> **Tip:** Many developers already have `GITHUB_TOKEN` set from `gh` CLI usage —
28+
> if so, you're already authenticated. No extra setup needed.
29+
30+
### Option 2: `amplifier init` setup wizard
31+
32+
```bash
33+
amplifier init
34+
# Select "GitHub Copilot" from the provider list
35+
# Launches browser OAuth flow if no token is set
36+
```
2837

29-
You must be authenticated to GitHub Copilot:
38+
### Option 3: `gh` CLI bridge
3039

3140
```bash
32-
copilot auth login
41+
export GITHUB_TOKEN=$(gh auth token)
3342
```
3443

44+
One command to bridge your existing `gh` CLI authentication into Amplifier.
45+
3546
## Installation
3647

37-
Register the module, install its dependencies, and set it as your active provider:
48+
GitHub Copilot is a well-known provider — `amplifier init` handles everything:
3849

3950
```bash
40-
amplifier module add provider-github-copilot \
41-
--source git+https://github.com/microsoft/amplifier-module-provider-github-copilot@main
51+
# Interactive setup — select Copilot from the provider list
52+
amplifier init
4253

43-
amplifier provider install github-copilot
54+
# Non-interactive — auto-detects GITHUB_TOKEN
55+
amplifier init --yes
56+
```
57+
58+
Or install manually:
4459

60+
```bash
61+
amplifier provider install github-copilot
4562
amplifier provider use github-copilot
4663
```
4764

48-
> **Note:** The `provider install` step is required to install the module's Python
49-
> dependencies (including the GitHub Copilot SDK) into the Amplifier environment.
50-
> The built-in providers skip this step because they are pre-installed during
51-
> `amplifier init`.
52-
53-
Or reference it directly in a bundle (no separate install needed):
65+
Or reference it directly in a bundle:
5466

5567
```yaml
5668
providers:

amplifier_module_provider_github_copilot/__init__.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,12 @@ def _find_copilot_cli(config: dict[str, Any]) -> str | None:
221221
"""
222222
Find the Copilot CLI executable path.
223223
224-
Uses the SDK's bundled CLI binary by default. Falls back to system PATH
225-
if the bundled binary is not found. The SDK bundles its own CLI binary
226-
which is version-matched, avoiding potential version mismatches.
224+
Resolution order:
225+
1. SDK bundled binary (import copilot -> copilot/bin/copilot)
226+
2. System PATH fallback (shutil.which)
227+
228+
The SDK bundles its own CLI binary which is version-matched,
229+
avoiding potential version mismatches with separately installed CLIs.
227230
228231
Args:
229232
config: Provider configuration (unused, kept for API compatibility)
@@ -232,14 +235,32 @@ def _find_copilot_cli(config: dict[str, Any]) -> str | None:
232235
Resolved CLI path, or None if not found
233236
"""
234237
try:
235-
# Let SDK use its bundled CLI - only fall back to system PATH
238+
try:
239+
from pathlib import Path
240+
241+
import copilot as _copilot_mod # type: ignore[import-untyped]
242+
243+
_mod_file = _copilot_mod.__file__
244+
if _mod_file is None:
245+
raise ImportError("copilot module has no __file__")
246+
_cli_bin = Path(_mod_file).parent / "bin" / "copilot"
247+
if _cli_bin.exists():
248+
cli_path = str(_cli_bin)
249+
_ensure_executable(cli_path)
250+
logger.debug(f"[MOUNT] Found SDK bundled CLI at: {cli_path}")
251+
return cli_path
252+
else:
253+
logger.debug("[MOUNT] SDK bundled CLI binary not found on disk")
254+
except ImportError:
255+
logger.debug("[MOUNT] copilot SDK not installed, trying PATH")
256+
236257
found = shutil.which("copilot") or shutil.which("copilot.exe")
237258
if found:
238259
_ensure_executable(found)
239-
logger.debug(f"[MOUNT] Found Copilot CLI at: {found}")
260+
logger.debug(f"[MOUNT] Found Copilot CLI in PATH at: {found}")
240261
return found
241262

242-
logger.debug("[MOUNT] Copilot CLI not found in PATH")
263+
logger.debug("[MOUNT] Copilot CLI not found via SDK or PATH")
243264
return None
244265

245266
except Exception as e:

amplifier_module_provider_github_copilot/client.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,13 @@ def _build_client_options(self) -> dict[str, Any]:
315315
316316
The SDK bundles its own CLI binary which is version-matched to the SDK.
317317
We always use the bundled CLI to avoid version mismatches and auth issues.
318+
319+
Token precedence (highest to lowest):
320+
1. config["github_token"] (explicit config)
321+
2. COPILOT_GITHUB_TOKEN env var (SDK-preferred)
322+
3. GH_TOKEN env var (gh CLI compat)
323+
4. GITHUB_TOKEN env var (most common)
324+
5. No token — SDK uses stored OAuth creds (use_logged_in_user=True)
318325
"""
319326
options: dict[str, Any] = {}
320327

@@ -327,6 +334,18 @@ def _build_client_options(self) -> dict[str, Any]:
327334
if self._config.get("cwd"):
328335
options["cwd"] = self._config["cwd"]
329336

337+
# Token resolution: config > COPILOT_GITHUB_TOKEN > GH_TOKEN > GITHUB_TOKEN
338+
token = self._config.get("github_token") or None
339+
if not token:
340+
for env_var in ("COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"):
341+
token = os.environ.get(env_var)
342+
if token:
343+
logger.debug(f"[CLIENT] Using token from {env_var}")
344+
break
345+
346+
if token:
347+
options["github_token"] = token
348+
330349
return options
331350

332351
async def _verify_authentication(self) -> None:
@@ -343,7 +362,9 @@ async def _verify_authentication(self) -> None:
343362
auth_status = await self._client.get_auth_status()
344363
if not auth_status.isAuthenticated:
345364
raise CopilotAuthenticationError(
346-
"Not authenticated to GitHub Copilot. Run 'copilot auth login' to authenticate."
365+
"Not authenticated to GitHub Copilot. "
366+
"Set GITHUB_TOKEN, run 'gh auth login', "
367+
"or run 'amplifier init' to authenticate."
347368
)
348369
logger.debug(f"[CLIENT] Authenticated as: {auth_status.login}")
349370
except CopilotAuthenticationError:

0 commit comments

Comments
 (0)