requestCopilotDeviceGrant function

Future<CopilotDeviceGrant> requestCopilotDeviceGrant({
  1. String clientId = copilotDeviceClientId,
  2. String scope = copilotDeviceScope,
  3. Client? client,
})

Requests a device code (goal: device_flow.go requestDeviceCode).

Throws CopilotDeviceFlowError with kind CopilotDeviceFlowErrorKind .endpointDisabled when GitHub rejects the app's device flow (a 404 or a JSON error body — the Phase-0 live finding), naming clientId and the paste-a-token fallback; transport errors carry the HTTP detail.

Implementation

Future<CopilotDeviceGrant> requestCopilotDeviceGrant({
  String clientId = copilotDeviceClientId,
  String scope = copilotDeviceScope,
  http.Client? client,
}) async {
  final transport = client ?? sharedProviderHttpClient();
  final http.Response response;
  try {
    response = await transport
        .post(
          Uri.parse(copilotDeviceGrantUrl),
          headers: const {'accept': 'application/json'},
          body: {'client_id': clientId, 'scope': scope},
        )
        .timeout(effectiveProviderConnectTimeout);
  } on CopilotDeviceFlowError {
    rethrow;
  } on Object catch (error) {
    throw CopilotDeviceFlowError(
      CopilotDeviceFlowErrorKind.transport,
      'Copilot device-code request failed: $error',
    );
  }
  final body = _decodeJsonObject(response);
  if (body == null) {
    throw CopilotDeviceFlowError(
      CopilotDeviceFlowErrorKind.transport,
      'Copilot device-code request returned a non-JSON response '
      '(HTTP ${response.statusCode}): ${response.body.trim()}',
    );
  }
  final errorField = body['error'];
  if (response.statusCode != 200 ||
      (errorField is String && errorField.isNotEmpty)) {
    throw _endpointDisabled(clientId, response.statusCode, response.body);
  }
  final deviceCode = body['device_code'];
  final userCode = body['user_code'];
  if (deviceCode is! String || userCode is! String) {
    throw CopilotDeviceFlowError(
      CopilotDeviceFlowErrorKind.transport,
      'Copilot device-code response had an unexpected shape: '
      '${response.body.trim()}',
    );
  }
  return CopilotDeviceGrant(
    deviceCode: deviceCode,
    userCode: userCode,
    verificationUri: switch (body['verification_uri']) {
      final String uri when uri.isNotEmpty => uri,
      _ => 'https://github.com/login/device',
    },
    expiresIn: switch (body['expires_in']) {
      final int n => n,
      _ => 900,
    },
    interval: switch (body['interval']) {
      final int n => n,
      _ => 5,
    },
  );
}