Add programmatic device-code login to CopilotClient
- Lingua principale
- Java
- Stelle
- 10.5k
- Fork
- 1.5k
- Merge medio
- 1g 11h
- PR unite (30g)
- 127
Descrizione
### Problem
`CopilotClient` exposes `GetAuthStatusAsync` so callers can detect an unauthenticated state, but there is no SDK method to *transition* the session into an authenticated one. The only auth inputs today are:
- `CopilotClientOptions.GitHubToken`
- `GITHUB_TOKEN` / `GH_TOKEN` env vars
- Pre-existing cached credentials under `~/.copilot/` from a prior interactive `copilot /login` run
This works on machines where credentials have already been seeded — typical on Windows where the Copilot Chat extension or `gh copilot` may have populated the cache — but on a fresh macOS or Linux install there is no in-SDK path to bootstrap auth. The only options are:
1. Require the caller to supply a Personal Access Token. PATs are blocked by policy in many environments and can't be issued at all for some account types.
2. Launch the bundled CLI binary (`runtimes//native/copilot`) out-of-band and have the user run `/login` interactively, then re-enter the SDK once the credential file lands on disk.
Option 2 forces every SDK consumer to:
- Locate the bundled CLI binary across RIDs.
- Manage an external process lifecycle (cancellation, orphan cleanup, exit-code handling).
- Surrender stdio to the embedded CLI, which precludes custom UX, headless flows, and structured logging.
- Bypass the SDK's normal RPC contract entirely for this one capability.
### Proposal
Add a programmatic device-code OAuth flow to `CopilotClient` that yields the verification URL and user code via a callback (or an `IAsyncEnumerable` of state events) so consumers can render the prompt in their own UI:
```csharp
public Task LoginAsync(
LoginOptions options,
CancellationToken cancellationToken = default);
public sealed class LoginOptions
{
/// Invoked once with the device code + verification URL the user must visit.
public Action? OnDeviceCode { get; init; }
/// Optional override for the OAuth client / scopes if the SDK supports more than one tier.
public string? ClientId { get; init; }
}
public sealed record DeviceCodePrompt(
string UserCode,
Uri VerificationUri,
TimeSpan ExpiresIn,
TimeSpan PollInterval);
public sealed record AuthResult(
bool Success,
string? AccountLogin,
string? FailureReason);
```
After `LoginAsync` returns successfully, `GetAuthStatusAsync` should reflect the new state and subsequent RPC calls (`models.list`, `session.send`, etc.) should succeed without restarting the client.
A complementary `LogoutAsync()` that clears the cached credentials would round out the surface but is lower priority.
### Why this belongs in the SDK
- **Parity with `gh auth`, Octokit's device flow, and the VS Code Copilot extension** — all of which provide a programmatic device-code path. The SDK is the only Copilot client that requires shelling out to authenticate.
- **No-PAT bootstrap** — enables the SDK to be used in environments where PATs are unavailable or restricted, without forcing a separate CLI install.
- **Single source of truth for credential storage** — the SDK already reads `~/.copilot/`; centralizing the write path here prevents callers from drifting if the storage format changes.
- **Cancellable, awaitable, structured** — replaces an opaque interactive subprocess with a normal async API that cooperates with `CancellationToken`, structured logging, and host-defined UX.
- **Headless / CI scenarios** — a callback-based device flow can be driven by automation (forwarding the code to chat, email, a webhook, etc.); an interactive subprocess cannot.
### Alternatives considered
1. **Document the shell-out** — pushes the same complexity into every SDK consumer.
2. **Expose a `LoginUri` / `LoginPoll` pair** instead of a single `LoginAsync` — more flexible but pushes polling logic onto callers.
3. **Reuse `gh auth` tokens** — only works where `gh` is installed and authorized for the right account; not portable and still needs an external tool.
A first-class `LoginAsync` on `CopilotClient` is the smallest API addition that closes the gap.
Guida per i contributori
Apri la guida per i contributori
Valutazione
Questa issue non è ancora stata valutata.